tags:

views:

254

answers:

2

I'm trying to log the input and output of a particular method to the database. I'd like to have this information in separate columns. I've investigated the PatternLayout and it seems that it only caters for a single %message parameter, meaning that if you do:

log.Debug("This is a message");

then log4net sees "This is a message" as the message to be logged. I want to do something like:

log.Debug(request, response);

Is this possible using log4net? Keep in mind that my goal is to have "request" and "response" in separate columns.

A: 

I've come up with one way to do this using custom PatternConverters

public class InputPatternConverter : PatternConverter
{
    private static string _input;

    public static string Input
    {
        get { return _input; }
        set { _input = value; }
    }

    protected override void Convert(System.IO.TextWriter writer, object state)
    {
        writer.Write(Input);
    }
}

public class OutputPatternConverter : PatternConverter
{
    private static string _output;

    public static string Output
    {
        get { return _output; }
        set { _output = value; }
    }

    protected override void Convert(System.IO.TextWriter writer, object state)
    {
        writer.Write(Output);
    }
}

Appender Specification:

<appender name="ADONetAppender" type="log4net.Appender.AdoNetAppender">
  <bufferSize value="1" />
  <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  <connectionString value="data source=servername;initial catalog=database;Integrated Security=SSPI;" />
  <commandText value="INSERT INTO RequestLog ([input], [output]) VALUES (@input, @output)" />
  <parameter>
    <parameterName value="@input" />
    <dbType value="String" />
    <size value="4000" />
    <layout type="log4net.Layout.PatternLayout">
      <converter>
        <name value="input" />
        <type value="InputPatternConverter, ApplicationName" />
      </converter>
      <conversionPattern value="%input" />
    </layout>
  </parameter>
  <parameter>
    <parameterName value="@output" />
    <dbType value="String" />
    <size value="4000" />
    <layout type="log4net.Layout.PatternLayout">
      <converter>
        <name value="output" />
        <type value="OutputPatternConverter, ApplicationName" />
      </converter>
      <conversionPattern value="%output" />
    </layout>
  </parameter>
</appender>

Call it using:

InputPatternConverter.Input = inputString;
OutputPatternConverter.Output = outputString;

XmlConfigurator.Configure();
ILog logger = LogManager.GetLogger(typeof(ApplicationClassName));
logger.Debug("");
ilitirit
I'd be concerned about threadsafety using those statics...
Arjan Einbu
+3  A: 

Your PatternConverter way is a step in the right direction, though the use of the static Input and Output properties makes it all a bit shaky (thread-safety wise).

The trick here is to realize that the message parameter on logger.Debug(...) is object and that you can pass in whatever you like.

You could define a custom message type

public class InputOutput
{
    public string Input {get;set;}
    public string Output {get;set;}
}

and then let your converters read either property

public class InputPatternConverter : PatternConverter
{
    protected override void Convert(System.IO.TextWriter writer, object state)
    {
        var msg = ((LoggingEvent)state).MessageObject as InputOutput;
        if (msg != null)
            writer.Write(msg.Input);
    }
}

public class OutputPatternConverter : PatternConverter
{
    protected override void Convert(System.IO.TextWriter writer, object state)
    {
        var msg = ((LoggingEvent)state).MessageObject as InputOutput;
        if (msg != null)
            writer.Write(msg.Output);
    }
}

the logging then becomes much cleaner

logger.Debug(new InputOutput { Input = ..., Output = ...});

your config would be the same.

A tip though is to subclass the PatternLayout and add the converters in the constructor of that class. That way you can also trim down your config. This will not cause you to loose the %message token, your %input and %output tokens will come in addition to all the tokens that PatternLayout supports. So you could actually have a pattern like this:

"%date %message %newline%newline %input %newline%newline %output

Here's a quick implementation of a custom pattern layout:

public class InputOutputPatternLayout : PatternLayout
{
    public InputOutputPatternLayout()
    {
        AddConverter("input", typeof(InputPatternConverter));
        AddConverter("output", typeof(OutputPatternConverter));
    }
}
Peter Lillevold
To make the usage less verbose, you could implement this as an extension method to the ILogger interface taking the two params. Then the usage would be something like: logger.Debug(input, output);
Arjan Einbu
One limitation though is that you lose the "@message" parameter, unless you add another pattern converter (or maybe subclass PatterLayout?) and another property. I haven't tested whether this works, but would it be advisable to override the ToString() method to return the @message (just to make it more generic) instead of having a separate pattern converter?
ilitirit
Oh and just a correction: "state as InputOutput" should be something like "((LoggingEvent)state).MessageObject as InputOutput" instead.
ilitirit