C# 如何在NLog中修改${stacktrace}布局渲染器

C# 如何在NLog中修改${stacktrace}布局渲染器,c#,logging,nlog,C#,Logging,Nlog,我开始使用NLog.Interface,这样我就可以使用ILogger接口,而不是NLog上提供的Logger类(主要用于单元测试) 当我使用${stacktrace}时,我得到如下结果: class MyClass { private static readonly logger = new NLogLogger(typeof(MyClass)); public void DoSomething() { logger.Info("Hello from DoSomethin

我开始使用
NLog.Interface
,这样我就可以使用
ILogger
接口,而不是
NLog
上提供的
Logger
类(主要用于单元测试)

当我使用
${stacktrace}
时,我得到如下结果:

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

  public void DoSomething()
  {
    logger.Info("Hello from DoSomething");
  }
}
…=>LoginViewModel.LogIn=>LoggerAdapter.Info

我想删除
LoggerAdapter.Info
部分(之所以包括它,是因为
NLog.Interface
wrapps
LoggerAdapter
中的
LoggerAdapter
类)


我如何才能做到这一点?

请参见下面的答案,以获取一个简单的示例,说明如何正确包装NLog记录器,从而保留呼叫站点信息:

下面是另一个关于包装NLog记录器的问题。实际上,问题不是关于包装NLog记录器,但我的回答指出了另一个答案的NLog记录器包装器实现中的一个问题

关键是根据NLog
Logger.Log
方法实现包装器的日志记录方法,并将包装器的
type
作为第一个参数传递

为了节省时间,我在这里发布了一个略为缩短的NLog Logger包装代码版本:

  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);
    }
  }
您可以像这样使用包装器:

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

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

或者您可以将记录器插入到类中。

您可以创建自定义LayoutRenderer,它将跳过一些堆栈帧:

[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;
            }
        }
    }
}
[LayoutRenderer(“stacktrace”)]
公共类CustomStackTraseLayoutRenderer:StackTraceLayoutRenderer
{
[默认值(0)]
公共int SkipFrames{get;set;}//可配置
受保护的覆盖无效附加(StringBuilder生成器,LogEventInfo logEvent)
{
int startingFrame=logEvent.UserStackFrameNumber+TopFrames-1;
如果(startingFrame>=logEvent.StackTrace.FrameCount)
startingFrame=logEvent.StackTrace.FrameCount-1;
bool first=true;
int endingFrame=logEvent.UserStackFrameNumber+SkipFrames;
对于(int i=开始帧;i>=结束帧;--i)
{
StackFrame f=logEvent.StackTrace.GetFrame(i);
开关(格式)
{
案例StackTraceFormat.Raw:
Append(f.ToString());
打破
箱子堆垛形扁平:
如果(!第一个)
builder.Append(this.Separator);
var type=f.GetMethod().DeclaringType;
builder.Append(type==null?“:type.Name);
建造商。附加(“.”);
Append(f.GetMethod().Name);
第一个=假;
打破
案例StackTraceFormat.DetailedFlat:
如果(!第一个)
builder.Append(this.Separator);
追加(“[”+f.GetMethod()+“]”);
第一个=假;
打破
}
}
}
}
添加自定义布局渲染器:

<extensions>
  <add prefix="custom" assembly="YourAssemblyName"/>
</extensions>
<targets>
  <target xsi:type="ColoredConsole" name="c"
          layout="${time} ${custom.stacktrace:skipframes=1} ${level} ${message}">
  </target>
</targets>

这是一种很好的跳过堆栈帧的方法。然而,NLog内置了这种能力。要使其(NLog的内置功能)在包装NLog记录器的情况下正常工作,必须使用Log方法并将包装器的类型作为第一个参数传递。NLog将遍历堆栈,直到声明类型等于传入类型的堆栈帧。下一帧将显示实际的呼叫站点。