Using NLog as a rollover file logger

31,201

Solution 1

Finally I have settled with size-based file archival. I use a trick to name the file after just the day of month and I needed the size-based file archival because it really helps when you logs begin to grow beyond some hundred mega-bytes. It helps to make - for example - 20 MB chunks of log, so one can easily take a quick look at it with a light weight tool like Notepad++.

It is working for almost a year now. Here is a simplified version of my NLog.config file:

<?xml version="1.0" encoding="utf-8" ?>
<nlog autoReload="true" throwExceptions="true"
      xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <variable name="LogDir" value="${specialfolder:folder=MyDocuments}/MyApp/Log"/>
  <variable name="LogDay" value="${date:format=dd}"/>
  <targets>
    <target name="LogTarget1" xsi:type="File" fileName="${LogDir}/${LogDay}.log" encoding="utf-8"
        maxArchiveFiles="10" archiveNumbering="Sequence" archiveAboveSize="1048576" archiveFileName="${LogDir}/{#######}.a" />
  </targets>
  <rules>
    <logger name="AppLog" writeTo="LogTarget1" />
  </rules>
</nlog>

This config makes 1 MB log file for each day of month and keep at most 10 archived 1 MB log chunks in My Documents\MyApp\Log folder; like 29.log, 30.log and 31.log.

Edit: It's for some time that I use this NLog.config file and it covers pretty much every cases that I need. I have different levels of logging from different classes in separate files and when they've got big, they will get archived based on size, in a hourly manner:

<?xml version="1.0" encoding="utf-8" ?>
<nlog autoReload="true" throwExceptions="true" internalLogFile="nlog-internals.log"
      xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <variable name="LogHome" value="${basedir}/Log"/>
  <variable name="DailyDir" value="${LogHome}/${date:format=yyyy}/${date:format=MM}/${date:format=dd}"/>
  <variable name="HourlyArchive" value="${DailyDir}/${date:format=HH}-Archive/${level}-${logger}-{#######}-archived.a"/>
  <variable name="AppLogPath" value="${DailyDir}/${level}-${logger}.log"/>
  <variable name="DataLogPath" value="${DailyDir}/_data/inouts-${shortdate}.log"/>
  <variable name="EventSource" value="Application" />
  <targets>
    <target name="AppAsyncTarget" xsi:type="AsyncWrapper">
      <target xsi:type="RetryingWrapper" retryDelayMilliseconds="3000" retryCount="10">
        <target xsi:type="File" fileName="${AppLogPath}" encoding="utf-8"
            maxArchiveFiles="50" archiveNumbering="Sequence" archiveAboveSize="1048576" archiveFileName="${HourlyArchive}"
            layout="`${longdate}`${level}`${message}" />
      </target>
    </target>
    <target name="DataAsyncTarget" xsi:type="AsyncWrapper">
      <target xsi:type="RetryingWrapper" retryDelayMilliseconds="1500" retryCount="300">
        <target xsi:type="File" fileName="${DataLogPath}" encoding="utf-8"
            layout="`${longdate}`${message}" />
      </target>
    </target>
    <target name="EventLogAsyncTarget" xsi:type="AsyncWrapper">
      <target xsi:type="RetryingWrapper">
        <target xsi:type="EventLog" source="${EventSource}" machineName="." />
      </target>
    </target>
  </targets>
  <rules>
    <logger name="Data" writeTo="DataAsyncTarget" final="true" />
    <logger name="Event" writeTo="EventLogAsyncTarget" final="true" />
    <logger name="*" writeTo="AppAsyncTarget" />
  </rules>
</nlog>

And in each class that I want a logging functionality, I put this:

static readonly Logger SlotClassLogger = LogManager.GetCurrentClassLogger();
static Logger ClassLogger { get { return SlotClassLogger; } }

Two additional loggers are for piling some data on a daily basis and writing to Windows Event Log; which are app-wide loggers:

public static Logger DataLog { get; private set; }
public static Logger AppEventLog { get; private set; }

And they should be initialize at app start:

DataLog = LogManager.GetLogger("Data");
AppEventLog = LogManager.GetLogger("Event");

Note: Sometimes on you app exit you get an exception produced by NLog. It's because something that is not initialized, can not get disposed! You have just write an empty entry into your logger at app start, say:

DataLog.Info(string.Empty);

I have added this size limitation so log file can be viewed in (say) Notepad on a low-end server, for quick reviews. You should modify them based on your needs.

Solution 2

I would suggest that you separate the problem into two different aspects:

  • Rolling to a new file name each day (bear in mind the time zone; UTC day perhaps?)
  • Deleting old log files

In my experience it would be worth keeping the date in the log file name, e.g.

debug-2010-06-08.log

That part should be easy with NLog, given the examples in the docs.

Now the second part can easily be done in a second thread or possibly even a completely different process - it just needs to see how many files are present and delete the oldest ones if necessary. Finding the "oldest" should be easy if the date is in the filename, even if you don't want to trust the file system information.

In fact, looking at the NLog documentation, it looks like the "time-based file archival" target may do exactly what you want... but even if it doesn't, the normal "one log file per day" approach and rolling your own clean-up should be easy.

Share:
31,201
Kaveh Shahbazian
Author by

Kaveh Shahbazian

Programming, FunctionalProgramming, Learning continuously about Clean Code, Teamwork, TDD, DDD/IDD, and Technical Debt

Updated on July 11, 2022

Comments

  • Kaveh Shahbazian
    Kaveh Shahbazian almost 2 years

    How - if possible - can I use NLog as a rollover file logger? as if:

    I want to have at most 31 files for 31 days and when a new day started, if there is an old day log file ##.log, then it should be deleted but during that day all logs are appended and will be there at least for 27 days.