Blog Archive

Thursday, June 28, 2012

MVC3 Log4Net Async and Customized Logging

Setup log4net in Web.Config is straight forward. Need to write Extension of ILog adding Task for Non-blocking Database write.

Web.Config

 <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler"/>
  </configSections>

  <log4net debug="false">

    <appender name="AdoNetAppender_SqlServer" type="log4net.Appender.AdoNetAppender">
 <bufferSize value="1" />
        <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
        <!--<connectionString value="Data Source=Pentium22;Initial Catalog=db;Integrated Security=True" />-->
      <connectionString value="Data Source=Pentium22;Initial Catalog=db;User ID=sa;Password=xxxxxx;" />
        <!--<commandText value="INSERT INTO LogMVC3 ([Date],[Thread],[Level],[Logger],[Message]) VALUES (@log_date, @thread, @log_level, @logger, @message)" />-->
      <commandText value="INSERT INTO LogMVC3A ([Date],[Message]) VALUES (@log_date, @message)" />
        <parameter>
          <parameterName value="@log_date" />
          <dbType value="DateTime" />
          <layout type="log4net.Layout.PatternLayout" value="%date{yyyy'-'MM'-'dd HH':'mm':'ss'.'fff}" />
        </parameter>
        <parameter>
          <parameterName value="@thread" />
          <dbType value="String" />
          <size value="255" />
          <layout type="log4net.Layout.PatternLayout" value="%thread" />
        </parameter>
        <parameter>
          <parameterName value="@log_level" />
          <dbType value="String" />
          <size value="50" />
          <layout type="log4net.Layout.PatternLayout" value="%level" />
        </parameter>
        <parameter>
          <parameterName value="@logger" />
          <dbType value="String" />
          <size value="255" />
          <layout type="log4net.Layout.PatternLayout" value="%logger" />
        </parameter>
        <parameter>
          <parameterName value="@message" />
          <dbType value="String" />
          <size value="4000" />
          <layout type="log4net.Layout.PatternLayout" value="%message" />
        </parameter>
      </appender>
      <root>
        <level value="All"/>
        <appender-ref ref="AdoNetAppender_SqlServer"/>
      </root>
 
  </log4net>

Global.asax

        protected void Application_Start()
        {
            ...
            log4net.Config.XmlConfigurator.Configure();
        }
Note that for console app use [assembly: log4net.Config.XmlConfigurator(Watch = true)] 
Extension for ILog:

    public static class Class1
    {
        public static void Info2(this ILog log, object Message)
        {
            Task.Factory.StartNew(() =>
                {
                    log.Info(Message);
                    Thread.Sleep(5000);

                });
        }
    }

Logging anywhere w/o blocking:

    public static class Class1
    {
        public static void Info2(this ILog log, object Message)
        {
            Task.Factory.StartNew(() =>
                {
                    log.Info(Message);
                    Thread.Sleep(5000);

                });
        }

        public static void Audit2(this ILog log, object auditData)
        {
            Task.Factory.StartNew(() =>
            {
                AuditDataWrapper adw = new AuditDataWrapper() { AuditData = auditData };
                log.Info(adw);
                Thread.Sleep(5000);

            });
        }

    }
    public class AuditDataWrapper
    {
        public object AuditData { get; set; }
    }

Note:

root/level/value can be 
OFF FATAL ERROR WARN INFO DEBUG ALL

bufferSize=1 means immediate write to DB, no delay/caching
Reference:

http://basquang.wordpress.com/2011/08/26/logging-using-log4net-in-asp-net-mvc-3/


Customization -AuditData Pattern Converter

    public class AuditDataPatternLayoutConverter : PatternLayoutConverter
    {
        protected override void Convert(System.IO.TextWriter writer, log4net.Core.LoggingEvent loggingEvent)
        {
            object obj = loggingEvent.MessageObject;
            Type t = obj.GetType();
            if (loggingEvent.Level != log4net.Core.Level.Info || t != typeof(AuditDataWrapper)) return;

            AuditDataWrapper adw = obj as AuditDataWrapper;
             if (adw.AuditData==null) return;
             XmlSerializer ser = new XmlSerializer(adw.AuditData.GetType());
                
                StringWriter sw = new StringWriter();
               
                try
                {
                    ser.Serialize(sw, adw.AuditData);
                    sw.Flush();
                    writer.Write(sw.ToString());
                }
                catch
                {
                    string dump = DumpProperties(adw.AuditData, adw.AuditData.GetType());
                   writer.Write(dump);
                }
                
            
        }

        string DumpProperties(object obj, Type t)
        {
            PropertyInfo[] pi = t.GetProperties(); 
            StringBuilder sb = new StringBuilder();
            foreach (PropertyInfo p in pi) 
                 sb.Append(p.Name + " :" + p.GetValue(obj, null) + "\r\n"); 

           return sb.ToString();
        }


    }

      <parameter>
        <parameterName value="@auditdata" />
        <dbType value="String" />
        <size value="4000" />
        <layout type="log4net.Layout.PatternLayout">
          <converter>
            <name value="auditdata" />
             <type value="MvcApplication3.AuditDataPatternLayoutConverter" />
          </converter>
          <conversionPattern value="%auditdata" />
        </layout>
      </parameter>

// Can put in any type of data into Audit

            log.Audit2(new TestData()
            {
                Name = "John",
                Age = 90,
                id = WindowsIdentity.GetCurrent(),
                Children = new List<TestData>() { new TestData() { Name = "Doe", Age = 12 }, new TestData() { Name = "Mat", Age = 9 } }
            });

    public class TestData
    {
        [System.Xml.Serialization.XmlIgnore]
        public WindowsIdentity id { get; set; }
        public string  Name { get; set; }
        public int Age { get; set; }
        public List<TestData> Children { get; set; }
    }



Customization -existing Log4Net converter

<commandText value="INSERT INTO LogMVC3 ([Date],[Thread],[Level],[Logger],[Message],[StackTrace],[AuditData],[UserName]) VALUES (@log_date, @thread, @log_level, @logger,@Message, @stacktrace,@auditdata,@username)" />


     <parameter>
        <parameterName value="@stacktrace" />
        <dbType value="String" />
        <size value="4000" />
        <layout type="log4net.Layout.PatternLayout" value="%exception" />
      </parameter>
      <parameter>
        <parameterName value="@username" />
        <dbType value="String" />
        <size value="4000" />
        <layout type="log4net.Layout.PatternLayout" value="%username" />
      </parameter>

%appdomain
%date 
%exception
%file
%identity  //slow
%level
%line 
%location 
%logger
%method
%message 
%newline
%timestamp
%type
%username  //slow
%utcdate

These can be used to format message:
     <parameter>
        <parameterName value="@message" />
        <dbType value="String" />
        <size value="4000" />
        <layout type="log4net.Layout.PatternLayout" value=" %timestamp [%thread] %-5level %logger{2} %ndc - %message%newline" />
      </parameter>

No comments: