There are few developers who haven’t used log4net, it’s just one of those essential utilities that every self respecting professional should know of. In this post, I’ll talk about customizing the logging process with custom properties.
Although log4net makes the life of a developer already quite easy, I prefer to add an additional wrapper around the logging functionality. One reason for this is the ability to use dependency injection. Here’s a simple interface:
As you see, there are 4 log levels that I expose, each of them having multiple overloads. You will also see some of the overloads contain a string parameter named ‘category’. This could represent anything, but I have chosen to use this category parameter as a way to query the logs by this field rather than certain words in the log message. Although you could format your messages in such a way that you prepend or append categories, chances are you’ll forget this in certain places. That’s where this blog post comes in. In what comes next, I’ll show you for two appenders how to add custom properties to your log messages. There’s also a custom property that will automatically be set without intermediation of the developer: the current user’s name. Let’s start off with the implementation of the ILogger interface:
There isn’t anything special about this class except the last method: SetCategory. This method takes one string parameter and doesn’t return anything. Instead, it sets a property in the log4net’s Logical Thread Context. This way we expose custom data in the “Category” accessor of this context. This can be done using the configuration file in your project. I have prepared two appenders that will use this setting.
The first one is a RollingFileAppender (=file) and the other is an AdoNetAppender (=database).
Here is the first one: the RollingFileAppender. There are two interesting things going on here:
The first one is a new layout pattern called HTTPUser that uses a custom converter to insert the value in the specified conversion pattern. What’s inside this code can be anything, but I’ve chosen to pass some user information to this field. I’ve added a class in the same assembly where I declared the interface and the implementation, and it inherits the PatternLayoutConverter class from the log4net assembly.
I assume this code will be called from a web application, otherwise this wouldn’t really work very well. Using the TextWriter instance that is passed in the Convert method, you can write anything you want, log4net will ensure what you write is placed in the correct location (that could either be a certain column in a database or a certain tab order in a file). Note that this code works for any appender, so it’s not reliant on a database schema, XML schema, … The second interesting point that I talked about earlier is the ability to specify a category in my Logging interface. You also saw how we passed this custom field to the log4net context. The only thing we need to do is tell log4net how and where to retrieve this information. This can be done as follows:
To show that these two interesting new features are platform agnostic, here’s the configuration for a database:
These two features are useful for customizing the log4net logging process. Especially when you log extensively, analyzing logs can be a time consuming and frustrating business. By structuring your data, you can query your data more easily (for instance, by category or by user) which in turn results into better and faster analysis of possible issues.