Why add logging to your application?
At some point an application may require information to be logged. There are many reasons why you may want to add logging to your application. A few are:
- Exception, Error, Debug Logging – This type of logging is generally used by the developer to troubleshoot issues. Debug level logging should be turned off before deploying the application. Log all unhandled exceptions in your global exception handler (don’t have one…you probably should). Logging too much in this category causes excessive “chatter” in the log data. This can inhibit finding the information you really desire.
- Security Audit Trail – Some applications require the ability to audit who used the application and what they did while using it. Depending on the requirements the audit trail may record who enters and leaves or it may be a complete record of all user interactions with the application.
- Usage Statistics – This type of logging is useful mostly for web applications. Gathering statistics about who is using your application can allow the application to be modified to suit their needs.
- Performance Benchmarks – When ever performance is a requirement, it should be logged. This allows performance statistics to be gathered directly from the log file.
Before adding logging to your application you should do a little research on available logging frameworks. There are pros and cons to using a framework. For most cases, there is no reason to re-invent the wheel. A well designed logging framework will offer you the ability to log to files, sockets, email, windows event… etc. There are many logging frameworks available. A few .NET logging frameworks are: log4net, .NET Logging Framework, NLog…etc. Many other exist, just do a quick search.
At work I have used log4net. It is a very complete and robust logging framework. I find that configuration can be a bit complex. Especially if you just want simple logging to file. One of my blogging goals is to discover new code and techniques. So in this blog I chose to use the .NET Logging Framework by written by Lorne Brinkman (TheObjectGuy).
The .NET Logging Framework has loggers that can write to the following:
- File on disk
- Windows Event log
- TCP socket
- Console, Debug, Trace
In this framework, each of these logger types implements an abstract Logger base class. The Logger base class provides the external interface for each logger.
To use the logging framework, simply instantiate and configure the logger types (file, tcp…etc) you need and then add them (using the AddLogger method) to a dictionary maintained by the CompositeLogger object. Then use the CompositeLogger object to create log entries (using the overloaded Log method) and it will enumerate through its logger collection ensuring that each logger type you created logs the new entry.
As with most logging frameworks, this one allows you to set the severity of each entry. The following are the available options: Debug, Info, Status, Warning, Error, Critical, and Fatal. During configuration or each logger, you also set the minimum severity level before an entry gets added to its log stream. It is important to place this minimum severity level in a configuration file (external to your application). This allows the logging level to be increased or decreased without the recompiling the application.
An important requirement of any logging framework is thread safety. Your application will generally have many threads (for example ASP.NET thread pool). The .NET Logging Framework handles thread safety using the following code:
protected internal bool Log(LogEntry aLogEntry)
return ShouldLog( aLogEntry ) ? DoLog( aLogEntry ) : true;
Notice the C# lock method. This statement ensures thread safety by only allowing one thread to run the logging code. All other threads will block.
There are a lot of features in a good logging framework. All most always you should utilize a framework for you logging needs.
Create Your Own Wrapper
Encapsulation is your friend! You should never directly call the logging framework in your code. Always wrap the logging framework with you own logging class. This will achieve two really important things:
- It will isolate you from the logging framework that you have selected. Who knows, maybe you will find a better logging framework next year. Then you will be happy that you encapsulated the logging framework calls. It is important that your logging class does not betray the choice of your logging framework. For instance, objects from the logging framework should not be passed outside the logging framework. This may mean, for instance, that your wrapper exposes its own severity enumeration that gets internally mapped to the severity of your logging library.
- Your logging class will be reusable in all your applications. Be careful that no application specific features creep into your generic logging wrapper. Keep it clean so that it can be reused.
A good design pattern for the logger wrapper class is a singleton. Throughout your application you only want one instance of the logger object. In addition, I like to make the logging wrapper a static class. In other word, all the methods in the class will be static. Look at the API of the logging framework that you selected to provide you with an idea about what methods your wrapper class must expose. Below is the wrapper class (Logger) and supporting structures (LogEntry and LogSeverity) that I created.
The Logger class creates a instance of the logging frameworks’ CompositeLogger class. This object is private and really does all the work. The LogXXX() methods call equivalent methods on the CompositeLogger. Currently my wrapper only adds a rolling file logger object. Other logging types could be easily added.
It is important that you put some thought into what you are going to log and the format of the content. You want this content to be easily parsed at a later time. Most logging frameworks allow you to configure the logging format. With the framework that I selected, you create a formatter class that derives from the LogEntryFormatter base class. Mine is shown below. Since these two classes derive from a base class in the logging framework, they are private to the logging wrapper. Never let the framework’s objects leave your wrapper.
I recommend separating each object that you want to parse by a tab character. Most other separation characters tend to show up in the content you are logging (spaces, commas…etc).
The Logger class provides two parsing methods ParseFile and ParseFilesInFolder to process log files and return a collection of LogEntry objects. Notice the LogEntry object that is returned is defined in the logging wrapper above. Once again, never let the frameworks’ objects leave your wrapper. The private SortList method is used to sort all the LogEntrys in the collection before it is returned. This collection can then be used by your application’s presentation layer to provide a representation of log data.
The Logger wrapper class can now be used in all my applications. I can also rest easy knowing the day that I decide to use a new logging framework the only code changes will be isolated to my wrapper class.