3

I started using NLog.Interface so that I can use an ILogger interface instead of the Logger class provided on NLog (mainly for unit testing)

When I use ${stacktrace}, I get something like this:

... => LoginViewModel.LogIn => LoggerAdapter.Info

I'd like to remove the LoggerAdapter.Info part (which is included because NLog.Interface wrapps the Logger class inside LoggerAdapter).

How could I do that?

Oscar Mederos
  • 29,016
  • 22
  • 84
  • 124

2 Answers2

4

You can create custom LayoutRenderer, which will skip some stack frames:

[LayoutRenderer("stacktrace")]
public class CustomStackTraseLayoutRenderer : StackTraceLayoutRenderer
{
   [DefaultValue(0)]
   public int SkipFrames { get; set; } // configurable

   protected override void Append(StringBuilder builder, LogEventInfo logEvent)
   {
       int startingFrame = logEvent.UserStackFrameNumber + TopFrames - 1;
       if (startingFrame >= logEvent.StackTrace.FrameCount)
           startingFrame = logEvent.StackTrace.FrameCount - 1;

        bool first = true;
        int endingFrame = logEvent.UserStackFrameNumber + SkipFrames;
        for (int i = startingFrame; i >= endingFrame; --i)
        {
            StackFrame f = logEvent.StackTrace.GetFrame(i);

            switch (Format)
            {
                case StackTraceFormat.Raw:
                    builder.Append(f.ToString());
                    break;
                case StackTraceFormat.Flat:
                    if (!first)
                        builder.Append(this.Separator);
                    var type = f.GetMethod().DeclaringType;
                    builder.Append(type == null ? "<no type>" : type.Name);
                    builder.Append(".");
                    builder.Append(f.GetMethod().Name);
                    first = false;
                    break;
                case StackTraceFormat.DetailedFlat:
                    if (!first)
                        builder.Append(this.Separator);
                    builder.Append("[" + f.GetMethod() + "]");
                    first = false;
                    break;
            }
        }
    }
}

Adding custom layout renderer:

<extensions>
  <add prefix="custom" assembly="YourAssemblyName"/>
</extensions>
<targets>
  <target xsi:type="ColoredConsole" name="c"
          layout="${time} ${custom.stacktrace:skipframes=1} ${level} ${message}">
  </target>
</targets>
Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
  • 1
    This is a nice approach for skipping stack frames. However, NLog has this capability built in. To make it (NLog's built in capability) work correctly in the case of wrapping the NLog Logger, one must use the Log method and pass the type of the wrapper as the first parameter. NLog will traverse up the stack until the stack frame whose declaring type is equal to the passed in type. The actual call site will be the next frame. – wageoghe Sep 27 '13 at 14:39
2

See this answer here on SO for a brief example on the correct way to wrap the NLog Logger such that the call site information is preserved:

Problem matching specific NLog logger name

Here is another question that deals with wrapping the NLog Logger. Actually, the question is not about wrapping the NLog Logger, but my answer points out a problem in another answer's implementation of an NLog Logger wrapper.

What is the best way of using NLog with MEF?

The key is implementing your wrapper's logging methods in terms of NLog Logger.Log method and passing the type of your wrapper as the first parameter.

To save you some time, I've posted a slightly shortened version of the NLog Logger wrapper code here:

  class NLogLogger : ILogger
  {
    private NLog.Logger logger;

    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);
    }
  }

You would use the wrapper like this:

class MyClass
{
  private static readonly logger = new NLogLogger(typeof(MyClass));

  public void DoSomething()
  {
    logger.Info("Hello from DoSomething");
  }
}

Or you could inject the logger into your class.

Community
  • 1
  • 1
wageoghe
  • 27,390
  • 13
  • 88
  • 116