Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

A Simple Asynchronous Logger in C#

4.88/5 (22 votes)
23 May 2020Ms-PL6 min read 60.2K   3.1K  
Clearcove.Logging is a very simple logging library designed to meet most logging needs with straight-forward licensing terms.

Introduction

I know what you’re thinking – does the world really need another logging library?

If you’re looking for a logging library in .NET, you have a lot of options. There is NLog, Log4Net, Enterprise Logging, Serilog and Common.Logging just to name a few that come to mind. It is not hard to find a logging library written by talented developers who have put a lot of time and effort into creating robust and feature-rich software.

Does this problem still need solving?

Background

Well, a few months ago, I found myself in the market for a logging library. I am the author of a commercial desktop application which is distributed over the Internet. As such, I had three hard requirements:

  1. Log entries should be written asynchronously. I’ve seen too many applications suffer extreme performance problems due to synchronous logging.
  2. The library should be as small as possible. I don’t want my users to have to download and load a 1 MB DLL for a feature as simple as logging. The smaller, the better.
  3. I didn’t want to increase the complexity of application licensing. Currently, my customers have to agree to my licensing terms. Adding a 3rd party component with separate licensing terms might mean additional work is required to evaluate my product. Maybe this is just paranoia, but I wanted to keep things simple.

I thought those were pretty straightforward requirements, but it turns out that I could not find anything which met my needs. In particular, I found the licensing terms of many log libraries unacceptable as I just didn’t want to be forced into distributing ‘another’ license.

So I wrote my own logging library – Clearcove.Logging. It’s really lightweight at only 83 lines of code. The full implementation is also in a single .cs file so that it can be reused without necessarily having to import a library. The code was written with VS 2017, but I have tried to write code that is compatible with earlier versions. The logging library targets .NET 2.0 to appeal to a wider audience.

I think this approach to logging is a good choice for:

  1. Applications which do not have complex logging requirements
  2. Small applications like utilities which can benefit from simplified deployment
  3. Situations where licensing complexity must be kept to a minimum

So, how does it work?

Using the Code

To start with, I thought about what information I wanted to log. I wanted a simple API that could be used to log information like a timestamp, logger name, thread ID and message. I’m very familiar with the Log4Net API and I borrowed heavily from it.

To declare and use a logger, you can use syntax like:

C#
var log = new Logger(typeof(Program));    // Class level declaration.
log.Error("My error message", exception); // Logging from within a method.
log.Info("My info message");

If you’ve used other logging libraries in the past, this syntax will hopefully be familiar.

Data Encapsulation

Next, I wanted to represent my log entries as a simple object. The primary reason for doing this is that I wanted my logger to be able to raise logging events. I sometimes use these events when creating unit and integration tests as I find it can be helpful. This is just a personal preference. If you’re not interested in raising logging events, then this code can be simplified.

Logging events are encapsulated in LogMessageInfo object which is implemented as:

C#
public sealed class LogMessageInfo
{
    public readonly DateTime Timestamp;
    public readonly string ThreadId;
    public readonly string Level;
    public readonly string Logger;
    public readonly string Message;
}

Writing Log Entries

So now, we get to the meat of it. The API implementation and data encapsulation parts of the code discussed above are verbose but very simple. Asynchronous logging however is a bit nuanced. For example, what happens if an exception is thrown that causes the application to shut down? How do we know that all log entries will be written in the order in which they were received? There are several ways to solve this problem. Clearcove.Logger solves it in a simple but somewhat inelegant way:

C#
static void Main(string[] args)
{
      var targetLogFile = new FileInfo("./MyApp.log");
      Logger.Start(targetLogFile); // Loggers will complains if you skip initialization
      try
      {
          Run(args);
      }
      finally
      {
          Logger.ShutDown(); // Removing this line may result in lost log entries.
      }
}

This is an example of where Clearcove.Logging deviates from other implementations such as Log4Net. We must tell our logger when to start and stop logging. We must do this before attempting to write any log entries to the log file. Putting the Logger.ShutDown() call in a finally statement should give our logger a chance to write all pending log entries to the log file before the application shuts down. There will, of course, be situations where our log entries will not be written. For example, if the machine were to lose power. If some of these edge cases are a concern for you, you may want to consider synchronous logging.

Clearcove.Logging implements asynchronous log writing by using a single System.Thread.Timer instance. The period on the thread timer is not set and therefore the timer will fire only once. After all pending log entries have been successfully written to the log file, the timer will be reset to fire in the next interval. This behaviour will be similar to setting a period on the timer but will prevent the timer from being fired multiple times in the event that an interval is delayed.

Finally, log entries are written to the file using a simple call to File.AppendAllText. This call is probably not the most efficient way to make multiple writes to a log file but was chosen based on a desire to keep the code as simple as possible.

So there you have it. A very simple logging implementation which is perfectly capable of meeting the logging needs of most applications. It works great for me and addresses all of my logging concerns while keeping my dependencies to a minimum.

Future Work

One of the nice things about having a simple logger is that it is easy to understand and can be quickly customized to meet your needs. Examples include rolling log files, synchronous logging, external configuration, etc. Implementation of these features is left as an exercise for the reader. Have fun!

A big disadvantage of this logger implementation is that it is .NET only. I plan to release a Java implementation of this logging library soon.

Also, note that some CodeProject users may post enhancements below.  I will try to merge changes where they do not increase complexity, but if you find this logger does not - quite - meet your needs, it may be worth reading the comments below.

Points of Interest

One thing that got me into this mess was a desire for simplified software licensing. I struggled with the best way to allow Clearcove.Logger to be given away for free without increasing licensing complexity. Based on my research, I believe the Ms-PL to be the most permissible license. It is simple, easy to read and understand, and importantly requests that your binary distributions are released “under a license that complies with this license”. In my mind, this statement is open to interpretation and gives you a great deal of flexibility. It is certainly my intention that the software be as free as I can make it while still giving you the protection you should be concerned about. If you have a more open license suggestion, please let me know.

History

  • 0.9 - I use a variant of this in production. Small changes have been made for the article, but I do not believe they will introduce new bugs.
  • 0.91 - Removed string interpolation to make the code easier to compile. Grammar changes to article.
  • 0.92 - Updated to use ISO-8601 date format based on suggestions.  Minor grammar changes to article.
  • 0.93 - Fixed an issue with logging exception stack traces.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)