9

最近刚刚开始尝试使用 NLog,我突然想到,我希望能够将标题信息添加到日志文件的顶部,例如:

Executable name
File version
Release Date
Windows User ID
etc...

经过一番搜索,我无法在现有的在线文档或代码论坛中找到任何表明此类功能的内容。这可能吗?我以前总是将此类信息包含在日志文件中,并且发现它在过去的许多场合中都非常有用,在客户现场获取有关生产问题的信息时。诚然,此功能是为解决方案定制的,而不是基于任何当前的 .NET 日志框架。

4

3 回答 3

19

在我的一位同事使用 log4net 创建的日志中复制页眉/页脚时,碰巧偶然发现了这一点。我从一些开源项目中找到了这个并将其改编为内部示例。我认为它应该很容易根据您的需要进行修改。

<target name="logfile2" xsi:type="File" fileName="Logs\NLogDemo2.txt">
  <layout xsi:type="LayoutWithHeaderAndFooter">
    <header xsi:type="SimpleLayout" text="----------NLog Demo Starting---------&#xD;&#xA;"/>
    <layout xsi:type="SimpleLayout" text="${longdate}|${level:uppercase=true}|${logger}|${message}" />
    <footer xsi:type="SimpleLayout" text="----------NLog Demo Ending-----------&#xD;&#xA;"/>
  </layout>
</target>

它给了我这样的输出:

----------NLog Demo Starting---------

2013-03-01 16:40:19.5404|INFO|Project.Form1|Sample informational message
2013-03-01 16:40:19.5714|WARN|Project.Form1|Sample warning message
2013-03-01 16:40:19.5714|ERROR|Project.Form1|Sample error message
2013-03-01 16:40:19.5714|FATAL|Project.Form1|Sample fatal error message
----------NLog Demo Ending-----------

我不知道为什么这似乎没有记录。我能找到的唯一参考是:https ://github.com/nlog/NLog/wiki/LayoutWithHeaderAndFooter

-乔迪

于 2013-03-01T23:42:16.270 回答
6

我不知道有一种方法可以很容易地做到这一点。话虽如此,您提供的所有示例都可以添加到每个日志消息中(或者通过一些自定义代码相当容易获得)。也就是说,每条记录的消息都可以通过 Layout 和 LayoutRenderers 标记为可执行名称、文件版本、发布日期、Windows 用户 ID 等。

这显然与仅在日志文件顶部创建标头不同,因此它可能对您没有用处。

另一方面,您可以使用 Pat在本文中的回答中提到的技术将多个布局渲染器与同一目标相关联。您可以定义一个布局,其中包含您在标题中需要的字段,并在 FilteringWrapper 中设置过滤器以仅将该布局应用于会话的第一条消息(或者您可以使用将其添加到输出文件的其他技术只有一次)。

使用他的 NLog.config 文件,这是您可以实现所需的一种方法。请注意,我没有尝试过这个,所以我不知道这个配置文件是否有效,或者如果是,它是否会生成你想要的结果。

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.mono2.xsd" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      autoReload="true" 
      internalLogLevel="Warn" 
      internalLogFile="nlog log.log" 
      > 
    <variable name="HeaderLayout" value="${processname} ${gdc:item=version} ${gdc:item=releasedate} ${windows-identity}" /> 
    <variable name="NormalLayout" value="${longdate} ${logger} ${level} ${message} /> 

    <targets async="true"> 
        <target name="file" xsi:type="File" fileName="log.log" 
                layout="${NormalLayout}"> 
        </target> 

        <target name="fileHeader" xsi:type="File" fileName="log.log" 
                layout="${HeaderLayout}"> 
        </target>      
    </targets> 

    <rules> 
        <logger name="HeaderLogger" minlevel="Trace" writeTo="fileHeader" final="true" />           
        <logger name="*" minlevel="Trace" writeTo="file" /> 
    </rules> 

</nlog> 

在您的代码中,您的启动逻辑可能如下所示:

public void Main()
{
  AddHeaderToLogFile();
}

public void AddHeaderToLogFile()
{
  Logger headerlogger = LogManager.GetLogger("HeaderLogger");

  //Use GlobalDiagnosticContext in 2.0, GDC in pre-2.0
  GlobalDiagnosticContext["releasedate"] = GetReleaseDate();    
  GlobalDiagnosticContext["version"] = GetFileVersion();     
  GlobalDiagnosticContext["someotherproperty"] = GetSomeOtherProperty();

  headerlogger.Info("message doesn't matter since it is not specified in the layout");

  //Log file should now have the header as defined by the HeaderLayout

  //You could remove the global properties now if you are not going to log them in any
  //more messages.
}

这里的想法是在程序启动时将文件版本、发布日期等放入 GDC。使用“HeaderLogger”记录器记录消息。该消息将使用“HeaderLayout”写入日志文件,因为“HeaderLogger”与“fileHeader”目标相关联,该目标与“HeaderLayout”相关联。标题布局中定义的字段将写入日志文件。后续日志消息,因为它们不会使用“HeaderLogger”,所以将使用“root”(*) 布局。它们将转到同一个文件,因为“file”和“fileHeader”目标最终都指向同一个文件名。

在我开始输入此响应之前,我不确定您是否可以轻松地将标头添加到日志文件中。输入了这个,我认为它实际上可能很容易!

祝你好运!

[编辑] 这样的事情可能会根据级别更改布局。在第一节中,我定义了几个变量,每个变量都定义了一个布局。在下一节中,我定义了几个目标,每个目标都使用相同的文件,但被过滤为只允许写入特定级别的消息。在最后一节中,我定义了一个规则,它将所有消息(因此是“*”记录器名称)发送到所有目标。由于每个目标都按级别过滤,“trace”目标将只写入“trace”消息等。因此,“trace”消息将使用“trace”布局编写,“debug”消息将使用“debug”编写布局等。由于所有目标最终都写入同一个文件,因此所有消息最终都会在同一个文件中。这个我没试过

<variable name="TraceLayout" value="THIS IS A TRACE: ${longdate} ${level:upperCase=true} ${message}" /> 
<variable name="DebugLayout" value="THIS IS A DEBUG: ${longdate} ${level:upperCase=true} ${message}" /> 
<variable name="InfoLayout" value="THIS IS AN INFO: ${longdate} ${level:upperCase=true} ${message}" /> 


<targets async="true"> 
    <target name="fileAsTrace" xsi:type="FilteringWrapper" condition="level==LogLevel.Trace"> 
        <target xsi:type="File" fileName="log.log" layout="${TraceLayout}" /> 
    </target> 
    <target name="fileAsDebug" xsi:type="FilteringWrapper" condition="level==LogLevel.Debug"> 
        <target xsi:type="File" fileName="log.log" layout="${DebugLayout}" /> 
    </target> 
    <target name="fileAsInfo" xsi:type="FilteringWrapper" condition="level==LogLevel.Info"> 
        <target xsi:type="File" fileName="log.log" layout="${InfoLayout}" /> 
    </target>  
</targets> 

<rules> 
    <logger name="*" minlevel="Trace" writeTo="fileAsTrace, fileAsDebug, fileAsInfo" /> 
</rules> 

(请注意,我在这里只包括了 3 个级别)。

展示了如何(如果可行的话)基于关卡应用不同的布局,这似乎是一个不寻常的用例。我并不是说这是一个好主意或一个坏主意,但我不能说我真的非常看到这样做。根据您希望最终输出的具体外观,我向您展示的内容可能是实现它的最佳方式,也可能不是。也许您可以发布一些示例来说明您希望输出的外观。

您也可以考虑接受我的原始答案,然后提出一个关于改变每个级别的输出布局的新问题,以便我们可以将该问题的讨论集中在级别/布局问题上。这是否有用取决于您。

这有效:

  <variable name="TraceLayout" value="This is a TRACE - ${longdate} | ${logger} | ${level} | ${message}"/>
  <variable name="DebugLayout" value="This is a DEBUG - ${longdate} | ${logger} | ${level} | ${message}"/>
  <variable name="InfoLayout" value="This is an INFO - ${longdate} | ${logger} | ${level} | ${message}"/>
  <variable name="WarnLayout" value="This is a WARN - ${longdate} | ${logger} | ${level} | ${message}"/>
  <variable name="ErrorLayout" value="This is an ERROR - ${longdate} | ${logger} | ${level} | ${message}"/>
  <variable name="FatalLayout" value="This is a FATAL - ${longdate} | ${logger} | ${level} | ${message}"/>
  <targets>
    <target name="fileAsTrace" xsi:type="FilteringWrapper" condition="level==LogLevel.Trace">
      <target xsi:type="File" fileName="xxx.log" layout="${TraceLayout}" />
    </target>
    <target name="fileAsDebug" xsi:type="FilteringWrapper" condition="level==LogLevel.Debug">
      <target xsi:type="File" fileName="xxx.log" layout="${DebugLayout}" />
    </target>
    <target name="fileAsInfo" xsi:type="FilteringWrapper" condition="level==LogLevel.Info">
      <target xsi:type="File" fileName="xxx.log" layout="${InfoLayout}" />
    </target>
    <target name="fileAsWarn" xsi:type="FilteringWrapper" condition="level==LogLevel.Warn">
      <target xsi:type="File" fileName="xxx.log" layout="${WarnLayout}" />
    </target>
    <target name="fileAsError" xsi:type="FilteringWrapper" condition="level==LogLevel.Error">
      <target xsi:type="File" fileName="xxx.log" layout="${ErrorLayout}" />
    </target>
    <target name="fileAsFatal" xsi:type="FilteringWrapper" condition="level==LogLevel.Fatal">
      <target xsi:type="File" fileName="xxx.log" layout="${FatalLayout}" />
    </target>
  </targets>


    <rules>
      <logger name="*" minlevel="Trace" writeTo="fileAsTrace,fileAsDebug,fileAsInfo,fileAsWarn,fileAsError,fileAsFatal" />
      <logger name="*" minlevel="Info" writeTo="dbg" />
    </rules>

我为每个日志级别设置了一个布局,在开头添加了一个描述消息级别的文字字符串(这是为了显示每个级别使用不同的格式)。每个 Layout 都与一个 FilteringWrapper 相关联,该 FilteringWrapper 根据消息的级别进行过滤,并将通过过滤器的任何消息引导到输出文件中。每个 FilteringWrapper 都包装同一个输出文件,因此所有日志消息都将记录到同一个文件中。

这是我用于测试的一段代码:

  logger.Trace("Trace msg");
  logger.Debug("Debug msg");
  logger.Info("Info msg");
  logger.Warn("Warn msg");
  logger.Error("Error msg");
  logger.Fatal("Fatal msg");

这是输出的样子:

This is a TRACE - 2010-11-22 13:20:00.4131 | NLogTest.Form1 | Trace | Trace msg
This is a DEBUG - 2010-11-22 13:20:00.4131 | NLogTest.Form1 | Debug | Debug msg
This is an INFO - 2010-11-22 13:20:00.4131 | NLogTest.Form1 | Info | Info msg
This is a WARN - 2010-11-22 13:20:00.4131 | NLogTest.Form1 | Warn | Warn msg
This is an ERROR - 2010-11-22 13:20:00.4131 | NLogTest.Form1 | Error | Error msg
This is a FATAL - 2010-11-22 13:20:00.4131 | NLogTest.Form1 | Fatal | Fatal msg

显然,我之前的配置信息中的问题是"writeTo"值之间的空间。我猜 NLog 对此很敏感。"writeTo=blah1, blah2, blah3". 当我将其更改为"writeTo=blah1,blah2,blah3"错误消失时,我有类似的东西。祝你好运!

于 2010-11-16T18:01:20.120 回答
2

您可以使用布局为每个“实例”(即应用程序第一次和应用程序最后一次写入任何给定文件)生成页眉/页脚部分,如先前答案所示

更多详情:

于 2013-08-19T20:11:21.527 回答