Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nlog - Generating Header Section for a log file

Tags:

c#

logging

nlog

Just recently got into experimenting with NLog, and it occurs to me that I would like to be able to add header information to the top a log file such as:

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

After some searching I have been unable to find anything in the existing on-line documentation or code forums which indicates this type of functionality. Is this possible? I have always previously included this sort of information in log files, and have found it useful on numerous occsions in the past, when sourcing information on production issues at customer sites. Admittedly, this functionality was custom built for the solutions and not based on any of the current .NET logging frameworks.

like image 945
Paul Johnson Avatar asked Nov 16 '10 16:11

Paul Johnson


2 Answers

Just happened to stumble on this while looking at replicating a header/footer in a log one of my co-workers created with log4net. I found this from some open-source project and adapted it as an internal example. I think it should be simple to modify for 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 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 to be undocumented. The only reference I could find was here: https://github.com/nlog/NLog/wiki/LayoutWithHeaderAndFooter

-Jody

like image 96
JKoplo Avatar answered Oct 13 '22 05:10

JKoplo


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

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

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

Using his NLog.config file, here is one way that you might achieve what you want. Note that I have not tried this, so I don't know if this config file is valid or, if it is, if 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 your code, your startup logic might look 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 put the file version, release date, etc in the GDC when the program starts. Log a message with the "HeaderLogger" logger. This message would be written to the log file using the "HeaderLayout" since the "HeaderLogger" is associated with the "fileHeader" target which is associated with the "HeaderLayout". The fields defined in the header layout are written to the log file. Subsequence log messages, since they will not use the "HeaderLogger", will use the "root" (*) layout. They will go to the same file since both the "file" and "fileHeader" targets ultimately point to the same filename.

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

Good luck!

[EDIT] Something like this might work to change the layout based on level. In the first section I have defined several variables, each of which defines a layout. In the next section I have defined several targets each of which uses the same file, but is filtered to only allow messages of a specific level to be written. In the final section I define a single rule that will send all messages (hence the "*" logger name) to all targets. Since each target is filtered by level, the "trace" target will write only "trace" messages etc. So, "trace" messages will be written using the "trace" layout, "debug" messages will be written using the "debug" layout, etc. Since all targets ultimately write to the same file, all messages will end up in the same file. I haven't tried this, but I think 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 have only included 3 levels here).

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

You might also consider accepting my original answer and then making a new question about varying the output layout per level so that we can focus the discussion in that question on the level/layout issue. It is up to you if that seems useful 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 for each logging level, adding a literal string at the beginning that describes the level of the message (this is to show that a different format is used for each level). Each Layout is associated with a FilteringWrapper that filters based on the level of the message and directs any messages that pass the filter to be logged in the output file. Each FilteringWrapper is wrapping the same output file, so all log messages will be logged to the same file.

Here is a section of code that I used 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 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 earlier config information was the space between the "writeTo" values. I guess NLog is sensitive to this. I had something like "writeTo=blah1, blah2, blah3". When I changed that to "writeTo=blah1,blah2,blah3" the error went away. Good luck!

like image 44
wageoghe Avatar answered Oct 13 '22 06:10

wageoghe