Bogey
Bogey

Reputation: 5734

log4net - Rendering an (object) message depending on the appender

I'm using log4net, and a custom logg facade practically identical to log4net.ILog to access it. I would like to log to a simple textfile, but also use a custom XML appender to write stuff to XML.

Now here's the issue. Say I'm doing something like

interface IMyILogFacade
{
// An implementation of this interface will eventually route this call to 
// log4net.ILogger.Trace
void Trace(string format, params object[] args);
}

class Test
{
  IMyILogFacade log; // assume this is given (injected)

  public void testMethod(Assembly assembly)
  {
string msg = "Entering Method 'testMethod'. Method Parameter 0 (assembly): {0}";
    log.Trace(msg, assembly);
  }
}

By default, log4net will render the message with a simple String.Format(msg, assembly), when accessing %message in any conversion pattern, or RenderedMessage explicitly.

This behaviour is fine for my text file log. However for my XML log, I want it to be rendered differently. In short, I would then like to reflect on the runtime type of the parameter (assembly) and dump all its public members, and its public members' public members, and.. so on... to a nested XML structure. So instead of rendering with String.Format(msg, assembly), I'd need to use something else, say String.Format(msg, MyXmlDumper.Dump(assembly)).

I haven't found any good way to make rendering depending on the type of the Appender though.

My current approach was to have my logger facade transform all calls to Trace, Debug, ... etc into an object of type LogMessage.

public class LogMessage
{
    public string message { get; protected set; }
    public object[] @params { get; protected set; }

    public LogMessage(string message, params object[] @params)
    {
        this.message = message;
        this.@params = @params;
    }
}

I'd then use a class implementing IObjectRenderer to render this. This would be the one for a simple ToString-like function

public class LogMessageStringRenderer : IObjectRenderer
{
    public void RenderObject(RendererMap rendererMap, object obj, TextWriter writer)
    {
        LogMessage logMessage = obj as LogMessage;

        if(logMessage == null)
        {
            throw new InvalidOperationException("LogMessageStringRenderer can only render objects of type LogMessage");
        } else 
        {
            writer.Write(
                String.Format(logMessage.message, logMessage.@params)
                ); 
        }
    }
}

and of course it'd be straightforward to create the same for creating my Xml dump.

This seems like a not very ideal solution though. For a start, these renderes can (as far as I know) only be attached to a log4net repository. Either by code, or in the config file as a root element like

<renderer renderedClass="LogMessage" RenderingClass="LogMessageStringRenderer"/>

But that means I'd have to create two logging repositories in my application; one for the text file appender, another one for the xml appender, and set their rendering objects accordingly.

This seems very and unnecessarily complex though (let alone I don't have a clue how to best use logging repositories at the moment).

Is there possibly any better solution to this problem? Maybe either a way to somehow select renderers on an appender-basis instead of for the whole repository. Or some conceptually entirely different solution I haven't though of.

Any pointers would be very appreciated.

Thanks

Upvotes: 0

Views: 2017

Answers (1)

Bogey
Bogey

Reputation: 5734

Classic, been spending the entire day on this and just after posting here I do find a solution...

I'll wrap my messages in the LogMessage class as posted above. Then I'll create a converted deriving from PatternLayoutConverter and apply it to the message parameter:

<param name="ConversionPattern" value="%date&#9;-&#9;%thread&#9;-&#9;%level&#9;-&#9;%-80logger&#9;-&#9;%-40method&#9;-&#9;%message%newline" />
  <footer value="&#13;&#10;&#13;&#10;&#13;&#10;"/>
  <converter>
    <name value="message" />
    <type value="MyMessageConverter"/>
  </converter>

If I'm lazy, I may created two converters, one for a ToString implementation, one for my Xml-based dumping. If I'm more ambitious, I may create a converter that allows to apply a custom IObjectRenderer in the log4net.config and uses this to render the message. My IObjcetRenderer implementations can then take care of transforming the LogMessage as described in my first post.

This should both work very nicely; will implement this next week, drop me a message if anyone in the future is looking for specific implementations.

Upvotes: 0

Related Questions