Skip to content

Create your own logging adapter

didii edited this page Apr 23, 2018 · 9 revisions

Creating your own custom tracer adapter is pretty easy. I'll walk you through the steps of creating one.

Step 1

Create a project for it. Add reference to your preferred logging framework. There is NO need to reference Tracer (or Fody, or Cecil) at all. It mostly works on conventions and a bit of a configuration.

Step 2

Add a public class that will serve as the logger adapter. The only requirement here is that it must contain some public instance methods for the trace logging. Namely it must have a method with public void TraceEnter(string methodInfo, string[] paramNames, object[] paramValues) signature and another one with public void TraceLeave(string methodInfo, long startTicks, long endTicks, string[] paramNames, object[] paramValues). These will be called runtime for each method entry and exit. Let's look at them in details.

TraceEnter will receive a string containing the method name and two arrays. One with the name of the arguments and one with the values. paramNames might be null meaning no argument at all. The list of arguments will also contain any byRef and will not contain any out argument. It's up to you to convert them into whatever format you want and send it to the actual log.

Tracer.Log4Net does something similar:

    public void TraceEnter(string methodInfo, string[] paramNames, object[] paramValues)
    {
        if (_logger.IsEnabledFor(Level.Trace))
        {
            if (paramNames != null)
            {
                var parameters = new StringBuilder();
                for (int i = 0; i < paramNames.Length; i++)
                {
                    parameters.AppendFormat("{0}={1}", paramNames[i], paramValues[i] ?? NullString);
                    if (i < paramNames.Length - 1) parameters.Append(", ");
                }
                LogUsingYourLoggingFwork(Level.Trace, methodInfo, String.Format("Entered into ({0}).", parameters));
            }
            else
            {
                LogUsingYourLoggingFwork(Level.Trace, methodInfo, "Entered into.");
            }
        }
    }

TraceLeave will receive a string containing the method name, the start ticks and end ticks of the call and two arrays for the return value and out arguments. The return value will have a null paramNames entry. If paramNames is null there's no return value or out argument.

A possible adapter would be (from Tracer.Log4Net):

    public void TraceLeave(string methodInfo, long startTicks, long endTicks, string[] paramNames, object[] paramValues)
    {
        if (_logger.IsEnabledFor(Level.Trace))
        {
            string returnValue = null;
            if (paramNames != null)
            {
                var parameters = new StringBuilder();
                for (int i = 0; i < paramNames.Length; i++)
                {
                    parameters.AppendFormat("{0}={1}", paramNames[i] ?? "$return", paramValues[i] ?? NullString);
                    if (i < paramNames.Length - 1) parameters.Append(", ");
                }
                returnValue = parameters.ToString();
            }

            LogUsingYourLoggingFwork(Level.Trace, methodInfo,
                String.Format("Returned from. ({1}). Time taken: {0:0.00} ms.",
                    ConvertTicksToMilliseconds(endTicks-startTicks), returnValue));
        }
    }

Step 3

Create a class for log manager adapter. The responsibility of this class is to return logger adapter instances. The Tracer weaver will add something similar to each class which needs logging

public MyClassNeedsLogging
{
private LogAdapter _log = LogManagerAdapter.GetLogger(typeof(MyClassNeedsLogging));
....
}

So you have to have a GetLogger static method on the log manager adapter which returns an adapter based on a type parameter. Mostly this should be straightforward like

public static class LogManagerAdapter
{
    public static LoggerAdapter GetLogger(Type type)
    {
        return new LoggerAdapter(type);
    }
}

Step4

If you want to ease a bit on your logging you can make use of the static to instance rewriting of Tracer. All you need to do is add static log methods and their instance counterparts. Create a static class which will hold your static log methods and properties. Add your log methods with void return value and with an empty body. Also add empty property getters. For example:

public static class Log 
{
    public static void SomethingImportant(string param, int otherParam) { }
    public static bool IsDebugLevelEnabled { get; }
....
}   

You need to create its counterpart in the log adapter (same class which handles TraceEnter and TraceLeave) with the same name and class+method name signature, plus its now instance method and has an additional first argument to it. For properties you need to create a property with the class+method name.

public class LogAdapter 
{
    public void LogSomethingImportant(string methodInfo, string param, int otherParam) 
    {
        //do the actual logging here
    }

    public bool LogIsDebugLevelEnabled 
    {
        get { 
          //do the actual thing here
        } 
    }
....
}

In its body implement the actual logging/logic.

Step5

If you want to use attributes to influence tracing you must define them. Tracer cares about the namespace and type name and enum int values. So copy over the TracerAttributes.cs from Log4Net adapter or copy from here.

namespace TracerAttributes {
     [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method|AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
     public class TraceOn : Attribute {
         public TraceTarget Target { get; set; }
         public TraceOn() {}
         public TraceOn(TraceTarget traceTarget) { Target = traceTarget; }
     }

     [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method|AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
     public class NoTrace : Attribute { }

     public enum TraceTarget { Public, Internal, Protected, Private  }
 }

Step6

Your adapter is now ready and can be used. Reference it from your project so you can start using your static log methods. To make it work add Fody.Tracer package reference to your project where you want to use the logging. It will also bring in Fody if it not yet there. Now you need to do some configuration and add to FodyWeavers.xml the reference to your adapter.

<Weavers>   
    <Tracer adapterAssembly="Tracer.Log4Net" 
      logManager="Tracer.Log4Net.Adapters.LogManagerAdapter" 
      logger="Tracer.Log4Net.Adapters.LoggerAdapter" 
      staticLogger="Tracer.Log4Net.Log">
         <TraceOn class="public" method="public" />
    </Tracer>
</Weavers>

adapterAssembly is the name of the adapter assembly. logManager is the full name (with namespace) of the adapter class. logger is the adapter class again with namespace. staticLogger is the static class with your log methods.

You can also make a NuGet package out of your adapter which will then do the configuration automatically. Take a look at the Tracer.Log4Net.Fody source how to do it.