How to Retain Callsite Information When Wrapping Nlog

How to retain callsite information when wrapping NLog

The problem is that your wrapper is not wrapping correctly. Here is an example of how to wrap NLog correctly, taken directly from the source tree of NLog:

using System;
using System.Text;
using NLog;

namespace LoggerWrapper
{
/// <summary>
/// Provides methods to write messages with event IDs - useful for the Event Log target.
/// Wraps a Logger instance.
/// </summary>
class MyLogger
{
private Logger _logger;

public MyLogger(string name)
{
_logger = LogManager.GetLogger(name);
}

public void WriteMessage(string eventID, string message)
{
///
/// create log event from the passed message
///
LogEventInfo logEvent = new LogEventInfo(LogLevel.Info, _logger.Name, message);

//
// set event-specific context parameter
// this context parameter can be retrieved using ${event-context:EventID}
//
logEvent.Context["EventID"] = eventID;
//
// Call the Log() method. It is important to pass typeof(MyLogger) as the
// first parameter. If you don't, ${callsite} and other callstack-related
// layout renderers will not work properly.
//
_logger.Log(typeof(MyLogger), logEvent);
}
}
}

The key is passing the type of your logger wrapper to the call to Log. When NLog tries to find the callsite, it goes up the stack until the first calling method whose declaring type is NOT the type passed to the Log call. This will be the code that is actually calling your wrapper.

In your case, your logger would look something like this:

    public void Log(LogType messageType, Type context, string message, Exception exception)
{
NLog.Logger logger = NLog.LogManager.GetLogger(context.Name);
LogLevel logLevel = LogLevel.Info; // Default level to info

switch (messageType)
{
case LogType.Debug:
logLevel = LogLevel.Debug;
break;
case LogType.Info:
logLevel = LogLevel.Info;
break;
case LogType.Warning:
logLevel = LogLevel.Warn;
break;
case LogType.Error:
logLevel = LogLevel.Error;
break;
case LogType.Fatal:
logLevel = LogLevel.Fatal;
break;
default:
throw new ArgumentException("Log message type is not supported");
}

//
// Build LogEvent here...
//
LogEventInfo logEvent = new LogEventInfo(logLevel, context.Name, message);
logEvent.Exception = exception;

//
// Pass the type of your wrapper class here...
//
logger.Log(typeof(YourWrapperClass), logEvent);
}

Nlog Callsite is wrong when wrapper is used

See my answer to this question:

Problem matching specific NLog logger name

I have copied the example code (for an abbreviated NLog wrapper) from that answer here to save some trouble:

  class NLogLogger : ILogger
{
private NLog.Logger logger;

//The Type that is passed in is ultimately the type of the current object that
//Ninject is creating. In the case of my example, it is Class1 and Class1 is
//dependent on ILogger.
public NLogLogger(Type t)
{
logger = NLog.LogManager.GetLogger(t.FullName);
}

//Trace, Warn, Error, Fatal eliminated for brevity

public bool IsInfoEnabled
{
get { return logger.IsInfoEnabled; }
}

public bool IsDebugEnabled
{
get { return logger.IsDebugEnabled; }
}

public void Info(string format, params object [] args)
{
if (logger.IsInfoEnabled)
{
Write(LogLevel.Info, format, args);
}
}

public void Debug(string format, params object [] args)
{
if (logger.IsDebugEnabled)
{
Write(LogLevel.Debug, format, args);
}
}

private void Write(LogLevel level, string format, params object [] args)
{
LogEventInfo le = new LogEventInfo(level, logger.Name, null, format, args);
logger.Log(typeof(NLogLogger), le);
}
}

Note that this answer was given in the context of NInject. The same principal applies to wrapping NLog even if you are not using NInject. The key is communicating to NLog the type of your wrapper.

This is on example of how to write an NLog wrapper correctly (i.e. to maintain call site info). The key is in the Write method. Notice how it uses NLog's Log method. Notice also that is passes the type of the wrapper class as the first parameter. NLog uses the type information to navigate up the call stack. As soon as it sees a method whose DeclaringType is the passed-in type (i.e. the type of the wrapper), it knows that the next frame up the stack is the calling method.

Also see this link (to NLog's source repository) for two more examples of "extending" Logger. One by wrapping, one by inheriting:

https://github.com/jkowalski/NLog/tree/master/examples/ExtendingLoggers

I am not 100% sure, but I think that you cannot simply wrap NLog and delegate the Info, Debug, Warn, etc method to NLog like this:

class MyNLogWrapper
{
private readonly Logger logger = LogManager.GetCurrentClassLogger();

public void Info(string msg)
{
logger.Info(msg);
}
}

You need a way to tell NLog the type of your wrapper and I think that you can only do that by calling NLog via the Logger.Log method (overloaded).

If this is not useful enough, post your wrapper for more help.

NLog: Recording Line number and calling method when using wrapper class for NLog

Got it! Did a better search and found this fantastic question and answer:

How to retain callsite information when wrapping NLog
You simply need to pass in your wrapper class type to the "Log" method.

For anyone interested, here's my new wrapper class. The last method uses the "typeof" idea.

public static class Logger
{
private static string _unclassified = "Unclassified";
[Obsolete("Please supply a LoggerName and LoggerLevel")]
public static void Log(string message)
{
Log(_unclassified,LogLevel.Info, message,null);
}
[Obsolete("Please supply a LoggerName")]
public static void Log(LogLevel level, string message)
{
Log(_unclassified,level,message,null);
}

[Obsolete("Please supply a LoggerName and LoggerLevel")]
public static void Log(Exception ex)
{
Log(_unclassified,LogLevel.Info, "",ex);
}

public static void Log(LoggerNames name, LogLevel level , string message)
{

NLog.LogLevel nLevel = NLog.LogLevel.FromString(level.ToString());
Log(name.ToString(), level, message);
}

public static void Log(LoggerNames name, LogLevel level, Exception ex, string message)
{

NLog.LogLevel nLevel = NLog.LogLevel.FromString(level.ToString());
Log(name.ToString(),level,message,ex);
}

private static void Log(string loggerName, LogLevel level, string message, Exception ex = null)
{
if (level == LogLevel.Warning)
level = LogLevel.Warn;
NLog.LogLevel nLevel = NLog.LogLevel.FromString(level.ToString());

LogEventInfo logEvent = new LogEventInfo(nLevel,loggerName,null,message,null,ex);
NLog.LogManager.GetLogger(loggerName).Log(typeof(Logger), logEvent);
}
}

CallSite MethodName allways renders as MoveNext

Have you looked at the Wiki-page for the ${callsite} layout-renderer:

https://github.com/NLog/NLog/wiki/Callsite-layout-renderer

Try to enable these options:

  • cleanNamesOfAnonymousDelegates
  • cleanNamesOfAsyncContinuations

Ex.

${callsite:className=True:includeNamespace=False:fileName=False:includeSourcePath=False:methodName=True:cleanNamesOfAnonymousDelegates=True:cleanNamesOfAsyncContinuations=True}

There is a pending task for NLog ver. 5.0 to have these enabled by default: https://github.com/NLog/NLog/issues/1798 (Minor breaking change)

Castle Windsor Logging Facility NLog - callsite info

It looks to me like the Castle NLogLogger wrapper that wraps the NLog Logger object is not implemented such the call site information is retained. See this link into the Castle repository for the implementation of the wrapper.

For completeness, here is an abbreviated example of the Castle implementation:

public class NLogLogger : ILogger
{
public NLogLogger(Logger logger, NLogFactory factory)
{
Logger = logger;
Factory = factory;
}

internal NLogLogger() {}

public bool IsDebugEnabled
{
get { return Logger.IsDebugEnabled; }
}
public void Debug(string message)
{
Logger.Debug(message);
}
}

The key problem is that the "Debug" method (all the rest of the logging methods) uses the corresponding methods on the NLog Logger object. The logging methods on the NLog Logger use the calling function/method as the call site (that is not 100% correct, but it is effectively what happens in this case). So, in the case of this wrapper, the call site will be the Castle NLog wrapper implementation. One correct way to write the wrapper such that call site is preserved is the use the Log method on the NLog Logger, passing the Type of the wrapper as the first parameter. NLog will go up the stack until the Type of the MethodInfo is the same as the Type passed into the Log method. The next method up the stack will be the call site.

In order to retain the call site information correctly, the Debug method should look something like this:

public void Debug(string message)
{
LogEventInfo logEvent = new LogEventInfo(LogLevel.Debug, Logger.Name, message);
Logger.Log(typeof(NLogLogger), logEvent);
}

See these links to answers that I have posted in the past about correctly wrapping NLog's Logger such that call site information is retained:

How to retain callsite information when wrapping NLog

Nlog Callsite is wrong when wrapper is used

See also this link to NLog's source repository for some examples of how to extend NLog's Logger.

Abstracting NLog via an interface but retaining the logging class

If you provide the wrapped-logger-type to the NLog-Logger-Log-method, then it will be ignored in the callsite-logic.

See also:

https://github.com/NLog/NLog/blob/4c0acefa3d394f768b46ac8ba0cb39a018565ce4/examples/ExtendingLoggers/LoggerWrapper/Program.cs#L72



Related Topics



Leave a reply



Submit