Category Archives: C#

log4net PatternLayout and PatternConverter

I have the following scenario:

I’m using Unity dependency injection container and interception techniques to log some WCF operation calls. I need to log the input parameters and also the result of these operations so I’ve implemented a CallHandler that I can apply to whatever methods that I need to intercept.

In this CallHandler I’ve defined a class called LogEntry and I’m logging with log4net. Something like this:


LogEntry logEntry = new LogEntry();
....
// set some base properties
logEntry.MethodName = input.MethodBase.Name;
logEntry.TypeName = input.Target.GetType().FullName;
...
// set input parameters
logEntry.Input = ....;
...
// set return value
logEntry.ReturnValue = result.ReturnValue;
.....
// set total method duration
logEntry.CallTime = stopwatch.Elapsed.TotalMilliseconds;
.....

// use log4net logger to log this information
logger.Debug(logEntry);

So this LogEntry class has some properties that will hold the information that I want to log. Now I need to capture these properties and configure the log4net appenders properly.

I’m using log4net PatternLayout and PatternConverter features. Let’s start with PatternLayout


///<summary>
/// Extended version of <see cref="PatternLayout"/>
/// </summary>


public class ExtendedPatternLayout : PatternLayout
{
 ///<summary>
 /// Initializes a new instance of the <see cref="ExtendedPatternLayout"/> class.
 /// </summary>;


 public ExtendedPatternLayout()
 {
   this.AddConverter(new ConverterInfo()
   {
     Name = "logEntry",
     Type = typeof(LogEntryConverter),
   });
 }
}

This layout just adds a new Converter named “logEntry” implemented by the class LogEntryConverter. There is no reference to LogEntry class here. And the LogEntryConverter class is here:


 /// <summary>
 /// Custom <see cref="PatternConverter"/> to handle <see cref="LogEntry"/>
 /// </summary>
 public class LogEntryConverter : PatternConverter
 {
   /// <summary>
   /// Converts the specified writer.
   /// </summary>
   /// <param name="writer">The writer.</param>
   /// <param name="state">The state.</param>
   protected override void Convert(System.IO.TextWriter writer, object state)
   {
       var loggingEvent = state as LoggingEvent;
       var logEntry = loggingEvent.MessageObject as LogEntry;

       if (logEntry != null)
       {
          switch (this.Option.ToLower())
          {
             case "calltime":
               writer.Write(logEntry.CallTime.HasValue ? logEntry.CallTime.Value : 0);
               break;
             case "methodname":
               writer.Write(logEntry.MethodName);
               break;
             case "returnvalue":
               writer.Write(SerializationService.SerializeToJSON(logEntry.ReturnValue));
               break;
             ..........
             ..........
             default:
               writer.Write(string.Empty);
               break;
          }
       }
    }
 }

I’ve made the code smaller but you need to check for null references!

I’m using a JSON serialization technique to grab the representation of the method’s return value.

I need to override the Convert method and access state object that is a log4net LoggingEvent object. This object has my logged object from the CallHandler which is a LogEntry object. Based on the Option property of the converter I can choose which property to write.

And finally I can configure my log4net like this


<layout type="ExtendedPatternLayout">
  <conversionPattern value="%logEntry{CallTime}" />
</layout>

You can see the conversionPattern follows this syntax: %convertername{option}

Custom properties log4net

I had the need to add some custom properties in my log4net messages.

I used the log4net contexts like this:


log4net.ThreadContext.Properties[MessageIdKey] = messageId;

And then updated my rolling file appender:


<!-- Rolling File Appender -->
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="log.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="5" />
<maximumFileSize value="10MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{dd-MM-yyyy HH:mm:ss,fff} (%logger) [%2thread]     [%property{MessageId}] %5level %message%newline" />
</layout>
</appender>

and my AdoNetAppender:


<!-- ADO Appender -->
 <appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
 <bufferSize value="5" />
 <lossy value="false" />
 <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
 <connectionString value="data source=localhost;initial catalog=MyDb;integrated security=true;" />
 <commandText value="INSERT INTO Log ([Date],[Thread],[Level],[Logger],[Message],[Exception],[MessageId]) VALUES (@log_date, @thread, @log_level, @logger, @message, @exception, @messageId)" />

 ......

 <parameterName value="@messageId" />
 <dbType value="String" />
 <size value="255" />
 <layout type="log4net.Layout.PatternLayout">
 <conversionPattern value="%property{MessageId}" />
 </layout>
 </parameter>

 </appender>