[ACCEPTED]-Nlog - Generating Header Section for a log file-nlog

Accepted answer
Score: 19

Just happened to stumble on this while looking 9 at replicating a header/footer in a log 8 one of my co-workers created with log4net. I 7 found this from some open-source project 6 and adapted it as an internal example. I 5 think it should be simple to modify for 4 your needs.

<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>

It gives me output that looks 3 like this:

----------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-----------

I have no idea why this seems 2 to be undocumented. The only reference I 1 could find was here: https://github.com/nlog/NLog/wiki/LayoutWithHeaderAndFooter

-Jody

Score: 6

I'm not aware of a way to do that very easily. Having 101 said that, all of the examples you give 100 are available (or pretty fairly easily available 99 with some custom code) to be added to each 98 log message. That is, each logged message 97 can be tagged with executable name, file 96 version, release date, windows user id, etc 95 via the Layout and LayoutRenderers.

This 94 is obviously not the same as just creating 93 a header at the top of the log file, so 92 it might not be useful to you.

On the other 91 hand, you could use a technique mentioned 90 in Pat's answer in this post to associate multiple layout 89 renderers with the same target. You could 88 define a layout that contains the fields 87 that you want in your header and set the 86 filter in the FilteringWrapper to only apply 85 that layout for the first message of a session 84 (or you might use some other technique that 83 it is added to the output file only once).

Using 82 his NLog.config file, here is one way that 81 you might achieve what you want. Note that 80 I have not tried this, so I don't know if 79 this config file is valid or, if it is, if 78 it will generate the results that you want.

<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> 

In 77 your code, your startup logic might look 76 like this:

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.
}

The idea here is that you would 75 put the file version, release date, etc 74 in the GDC when the program starts. Log 73 a message with the "HeaderLogger" logger. This 72 message would be written to the log file 71 using the "HeaderLayout" since 70 the "HeaderLogger" is associated 69 with the "fileHeader" target which 68 is associated with the "HeaderLayout". The 67 fields defined in the header layout are 66 written to the log file. Subsequence log 65 messages, since they will not use the "HeaderLogger", will 64 use the "root" (*) layout. They 63 will go to the same file since both the 62 "file" and "fileHeader" targets 61 ultimately point to the same filename.

Before 60 I started typing this response I wasn't 59 sure how easily you could accomplish adding 58 a header to your log file. Having typed 57 this, I think that it might actually be 56 pretty easy!

Good luck!

[EDIT] Something like 55 this might work to change the layout based 54 on level. In the first section I have defined 53 several variables, each of which defines 52 a layout. In the next section I have defined 51 several targets each of which uses the same 50 file, but is filtered to only allow messages 49 of a specific level to be written. In the 48 final section I define a single rule that 47 will send all messages (hence the "*" logger 46 name) to all targets. Since each target 45 is filtered by level, the "trace" target 44 will write only "trace" messages 43 etc. So, "trace" messages will 42 be written using the "trace" layout, "debug" messages 41 will be written using the "debug" layout, etc. Since 40 all targets ultimately write to the same 39 file, all messages will end up in the same 38 file. I haven't tried this, but I think 37 that it will probably work.

<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> 

(Note that I 36 have only included 3 levels here).

Having 35 shown how (if it works, anyway) to apply 34 a different layout based on level, this 33 seems like sort of an unusual use case. I'm 32 not saying that it is a good idea or a bad 31 idea, but I can't say that I have really 30 seen this done very much. Depending on 29 exactly how you want your final output to 28 look, what I have shown you may or may not 27 be the best way to achieve it. Maybe you 26 could post some examples of how you want 25 your output to look.

You might also consider 24 accepting my original answer and then making 23 a new question about varying the output 22 layout per level so that we can focus the 21 discussion in that question on the level/layout 20 issue. It is up to you if that seems useful 19 or not.

This works:

  <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>

I have set up one Layout 18 for each logging level, adding a literal 17 string at the beginning that describes the 16 level of the message (this is to show that 15 a different format is used for each level). Each 14 Layout is associated with a FilteringWrapper 13 that filters based on the level of the message 12 and directs any messages that pass the filter 11 to be logged in the output file. Each FilteringWrapper 10 is wrapping the same output file, so all 9 log messages will be logged to the same 8 file.

Here is a section of code that I used 7 for testing:

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

And here is what the output 6 looks like:

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

Apparently the problem in my 5 earlier config information was the space 4 between the "writeTo" values. I guess NLog is sensitive 3 to this. I had something like "writeTo=blah1, blah2, blah3". When I 2 changed that to "writeTo=blah1,blah2,blah3" the error went away. Good 1 luck!

Score: 2

You can generate a header/footer section 3 per "instance" (i.e. the first time the app and the last 2 time the app writes to any given file) using 1 Layouts as indicated by previous answer:

More detail:

More Related questions