Tuesday, December 4, 2012

Logging Complete Objects and Properties with Log4net

I came across a code sample that gave me an idea.

The problem

At times we need to log objects into a debug or error log - the full object. object.ToString() doesn't give us a log of all the properties.


The Solutions

1. Create an extension method for .ToString() for all objects but the impact of that is far reaching. Let's scratch that method.
2. Create an object dumper and tell log4net to use it. We can use the ObjectDumper code from Microsoft

I found this code at: http://codingcockerel.co.uk/2008/12/28/add-logging-to-your-application-using-log4net-part-two/
This doesn't quite get us there, but almost.

We'll need the code ObjectDumper.cs at various locations - here's one that will work:
ObjectDumper.cs


public class Log4NetObjectLogger : IObjectRenderer
    {
        public void RenderObject(RendererMap rendererMap, object obj, TextWriter writer)
        {
            var ex = obj as Exception;

            //if its not an exception, dump it. If it's an exception, log extended details.
            if (ex == null)
            {
                //by default log up to 10 levels deep.
                ObjectDumper.Write(obj,10,writer);
            }
            else
            {
                while (ex != null)
                {
                    RenderException(ex, writer);
                    ex = ex.InnerException;
                }
            }
        }

        private void RenderException(Exception ex, TextWriter writer)
        {
            writer.WriteLine(string.Format("Type: {0}", ex.GetType().FullName));
            writer.WriteLine(string.Format("Message: {0}", ex.Message));
            writer.WriteLine(string.Format("Source: {0}", ex.Source));
            writer.WriteLine(string.Format("TargetSite: {0}", ex.TargetSite));
            RenderExceptionData(ex, writer);
            writer.WriteLine(string.Format("StackTrace: {0}", ex.StackTrace));
        }

        private void RenderExceptionData(Exception ex, TextWriter writer)
        {
            foreach (DictionaryEntry entry in ex.Data)
            {
                writer.WriteLine(string.Format("{0}: {1}", entry.Key, entry.Value));
            }
        }
    }


Now we can log our FULL object details up to 10 levels deep by doing simply the following:

log.Debug(customer);
In order to configure the custom logger, edit your app.config or web.config and find the <log4net> element. Add your custom logger as follows:
<log4net>

    <renderer renderedclass="System.Object" renderingclass="YourProjectName .NameSpace.ToYourClass.Log4NetObjectLogger, YourProjectName"></renderer>

    <root>.....</root>



Note the format is YourClassFullNameSpace.YourLogger, YourProjectName

That's all there is to it - now you can view the state of your object at any time by a simple log call.


6 comments:

  1. Great idea! Takes a lot of the pain out of trying to log every property or implement ToString() on every object. Nice!

    ReplyDelete
  2. Thangs for that! You schould change the wrong attribute case from renderingclass to renderingClass and renderedclass to renderedClass!

    ReplyDelete
  3. There's a slight change that is necessary in the RenderObject method:

    if (obj is SystemStringFormat)
    {
    writer.Write(obj.ToString());
    }
    else
    {
    //...Current RenderObject implementation here
    }

    ReplyDelete
  4. Hi, I need to pass a nulleable value to a DB, can you put an example?
    The Log4Net throw an exception whit a null value, the field in the table accept nulls.

    Thanks.

    ReplyDelete
  5. HI, It was the idea I was looking for. However I m not clear on this will work. Where are these classes supposed to be kept. I kept them in another project in the solution and they are never invoked.Not clear on this part.

    ReplyDelete