Practical .NET

A Simple and Sophisticated Logging Tool

When things go wrong in production, logging provides a way of going back through an application's history to find out what happened. Here's the simplest possible way to use Peter's favorite third-party logging utility: NLog.

Many years ago, for one of my clients, I wrote a logging system to track critical information about what our application was doing in production. Even back then, I suspect it was a foolish thing to do -- there was almost certainly a third-party tool I could've used that would've saved me a lot of typing.

Since then I've gotten smarter and, over the years, used a variety of tools for creating logs, most of them open source (by which I mean: Free!). My current favorite is NLog, which is easy to set up, easy to integrate with my applications, easy to configure and (most important!) easy to reconfigure in production. This column won't show you all the features of NLog, but it will show you the simplest way to use it.

Configure a Class for Logging
With NLog, you first establish what kind of data a class will write to a log. Once you've taken care of that, you can trigger logging from the application that uses that class whenever you need it.

This means the first step in using NLog is to configure a class to write log data. You'll need to get NLog through NuGet, add a reference to NLog to your class library and stick an Imports/using statement for the NLog namespace at the top of each class that will write to the log.

In each class, you need to get a reference to the NLog class logger. You get this reference by calling the GetCurrentClassLogger method on NLog's LogManager class (it's a static method, so you don't need to instantiate LogManager). That reference should be stored in a Shared/static variable of type Logger.

Here's an example from a class in one of my recent projects:

Public Class PetersClass
  Private Shared MyLogger As Logger = LogManager.GetCurrentClassLogger()

Now that you have a reference to the class logger, you can use it to write a log message by calling the Logger object's Log method from a method of your own in the same class (the application using your class will decide when to call your logging method). Here's a method that passes to the Log method the logging level passed to my method and a message made up of four properties from the class, separated by commas:

Public Sub LogMe(Level As NLog.LogLevel)
  MyLogger.Log(Level, String.Format("{0},{1},{2},{3}",
                          Me.Company,
                          Me.SiteId.Trim,
                          If(IsDBNull(Me.UnitId), "", Me.UnitId.Trim),
                          Me.ReadDate.ToShortDateString))
End Sub

I'm using a comma-delimited format here because many of my clients want their logging output in .CSV files (they load the files into Excel for analysis). NLog doesn't care about what format you use, however, and you can use any one that makes sense to you (I've had some clients that wanted XML, for example).

Now that you've configured a class for logging, you'll want to actually initiate that logging from your application. The simplest way to do that is to call your class' log method. In this example, I pass to my LogMe method a LogLevel of Info (other options include Debug, Error and Fatal):

Dim pc As PetersClass
pc = New PetersClass()
ps.LogMe(LogLevel.Info)

Configuring NLog
Now you're ready to define the log files where your messages will be written and how those files are to be managed. Again, you have options here, but I prefer to do it in the NLog.config file (this file must be in the same folder as your application's .EXE file).

In the NLog.config file you establish what log files you'll write by adding target elements inside the file's targets element. A target can establish two things: the file to be written to and the wrapper that controls access to the file. I typically begin with a target element that establishes that my log files are to be written to asynchronously. This example establishes a target called asyncPrimaryLog that wraps my log file with NLog's AsyncWrapper:

<targets>
  <target name="asyncPrimaryLog" xsi:type="AsyncWrapper">

Within the target element that defines the wrapper to use, you add another target element whose attributes control the log file. The example in Listing 1 establishes a target called PrimaryLogFile.

Listing 1: Establishing a Target Called PrimaryLogFile
   <target name="PrimaryLogFile"
            xsi:type="File"
            fileName="${basedir}/logs/Presence(${shortdate}).csv"
            archiveEvery="Day"
            archiveAboveSize="5120000"
            archiveOldFileOnStartup="false"
            archiveNumbering="DateAndSequence"
            archiveFileName="${basedir}/Logs/Archive/Presence(${shortdate}).{#####}.csv"
            layout="${shortdate},${time},${message}" 
            header="LogDate,LogTime,Company,SiteId,UnitId,ReadDate"
            concurrentWrites="true"
            keepFileOpen="false"
            encoding="iso-8859-2"     
  </target>
</target>

In this example, the type attribute specifies that my log messages are to be written to a file. That file's name is specified in the fileName attribute where I use some of NLog's predefined tokens (I put the log file in a subfolder of the folder with the NLog config file and insert the current date into the file name).

The various archive* attributes that follow specify when NLog is to start a new log file. When NLog does start a new file, it first makes a copy of the existing log file (the name and location of that archive file is also specified in the archive* attributes). In this example NLog is to create a new log file both every day and when the file exceeds a specified size. The copy that NLog creates first is to be put in a subfolder called Archive with a sequence number inserted into the file name just before the .csv extension.

Further down, the layout attribute controls what data goes in the line written to the log file by NLog. In my example, I'm using NLog's shortdate, time, and message tokens to have the date, time, and the message I passed to Logger's Log method inserted into the line.

The value in the header attribute is written once when the file is created and all subsequent log entries are added after the header line. Here, I'm using the header attribute to write out a set of comma-delimited headings that will line up with my data (remember that the message I write out already has several comma-delimited fields in it).

Picking the Log File to Write
Finally, in the NLog.config file you need a set of rules to control which target is to be used. This example says that the asyncPrimaryLog target I just defined is to be used whenever the message level is set to Info:

<rules>
  <logger name="*" level="Info" writeTo="asyncPrimaryLog" />
</rules>

There are lots of reasons to like NLog. For example, that Log method I've added to my class isn't tied to NLog: I can use it anywhere that makes sense, with or without NLog. What's more important to me is that I don't have to recompile or re-deploy my application to change how my log files are managed. To change the log file names, format of the lines in the log, the log's archiving rules, or any of the other options I've discussed, I just need to make a change to NLog.config.

As usual with this kind of introduction, there's a great deal more to NLog than I've discussed here (certainly, far more than I built into my custom logging system). However, with very little work you can have a very sophisticated logging system working for you so that you can spend your time meeting your deadlines. Who knows? Later, you might have some spare time to explore NLog and take advantage of more of its features -- if you ever need more than you've seen here.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Full Stack Hands-On Development with .NET

    In the fast-paced realm of modern software development, proficiency across a full stack of technologies is not just beneficial, it's essential. Microsoft has an entire stack of open source development components in its .NET platform (formerly known as .NET Core) that can be used to build an end-to-end set of applications.

  • .NET-Centric Uno Platform Debuts 'Single Project' for 9 Targets

    "We've reduced the complexity of project files and eliminated the need for explicit NuGet package references, separate project libraries, or 'shared' projects."

  • Creating Reactive Applications in .NET

    In modern applications, data is being retrieved in asynchronous, real-time streams, as traditional pull requests where the clients asks for data from the server are becoming a thing of the past.

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

Subscribe on YouTube