Introduction
(For the latest changes, please see the History section below)
In this article, I'll show you a class that simplified my life in the last years when it comes to logging (error) messages from a .NET application, no matter whether it is a Windows GUI application, a Windows Service or a Windows web application.
By using the single class described in this article you should be able to provide meaningful logging information in every state of your application with a minimum of effort and a maximum of flexibility.
Background
Again (like in some of my other articles), I did not write the logging part itself from scratch but rather added my own code to the popular and well-tested superb logging framework LOG4NET.
The main class for logging is the class LogCentral
. I tried to hide all the LOG4NET classes and interfaces inside my class so that the user of the LogCentral
class never gets in direct contact with the LOG4NET services. The reason was to allow maximum flexibility in that if I ever want to change the underlying logging engine to another engine, no code changes other than inside the LogCentral
class itself are necessary.
Please note: A possible negative effect of the hiding of the LOG4NET is the fact that not all of the extended LOG4NET features are available to the user of the LogCentral
class. In practice this was never a problem inside my small- to mid-size projects, but could be if you try to do logging to the extreme. In such cases it would probably be better to directly use a logging framework like LOG4NET and do not use the LogCentral
class.
Supported Features
The LogCentral
class provides the following features:
To log a message, you simply use the static property LogCentral.Current
to access an always-available instance of the logging class.
The LogCentral
class provides easy access to actually log a message by providing overloads for several logging types.
Example: The following call logs an informational message.
...
LogCentral.Current.LogInfo(
"This is an informational message." );
...
The following functions are defined inside the LogCentral
class (parameters are omitted for better readability):
public void LogInfo()
- Logs an informational message.
public void LogError()
- Logs an error message, e.g. inside a catch block.
public void LogDebug()
- Logs a debug message that should not appear in release builds (it is up to you to properly configure the class through the configuration file so that the debug message really is not logged during release builds, though).
public void LogWarn()
- Logs a warning message.
public void LogFatal()
- Logs a fatal message.
Standard configuration through the known LOG4NET XML configuration schema
Although I said that the LogCentral
class hides all the LOG4NET code from the user of the class, I did not provide my own configuration file syntax. Therefore, the configuration for the LogCentral
class is exactly the same as for the LOG4NET itself. All documentation for configuring the LOG4NET framework applies to the LogCentral
class as well.
An example configuration inside your standard application configuration file (namely "YourExeName.exe.config" for Windows executables or "Web.config" for Web Services and web applications) might look like following:
="1.0" ="utf-8"
<configuration>
<configSections>
<section
name="log4net"
type="System.Configuration.IgnoreSectionHandler" />
</configSections>
<log4net>
<appender
name="RollingLogFileAppender"
type="log4net.Appender.RollingFileAppender">
<param name="File" value="Test.log" />
<param name="AppendToFile" value="true" />
<param name="MaxSizeRollBackups" value="9" />
<param name="MaximumFileSize" value="10MB" />
<param name="RollingStyle" value="Size" />
<param name="StaticLogFileName" value="true" />
<layout type="log4net.Layout.PatternLayout">
<param
name="Header"
value="\r\n\r\n---------------------------------------------\r\n" />
<param
name="Footer"
value="\r\n---------------------------------------------\r\n\r\n" />
<param name="ConversionPattern" value="%d [%t] %-5p - %m%n" />
</layout>
</appender>
<appender
name="ColoredConsoleAppender"
type="log4net.Appender.ColoredConsoleAppender">
<mapping>
<level value="ERROR" />
<foreColor value="White" />
<backColor value="Red" />
</mapping>
<mapping>
<level value="DEBUG" />
<backColor value="Green" />
</mapping>
<mapping>
<level value="INFO" />
<foreColor value="White" />
</mapping>
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%-5p: %m%n" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="RollingLogFileAppender" />
<appender-ref ref="ColoredConsoleAppender" />
</root>
</log4net>
</configuration>
In addition to the logging destinations ("appenders") defined in your configuration file, the LogCentral
class tries to log each message to the following destinations as well:
A common requirement in my applications was to provide additional information to the messages that actually get logged, depending on the current context of the application. E.g. when an SQL query failed, I wanted to log the actual SQL query too, not just the exception that was raised during the execution of the SQL query.
For such scenarios, I added support to provide event handlers by the application to the LogCentral
class that get called just before a message is actually being logged. The handler can add additional information to the message being logged.
Example
The following code adds a handler for the event. Usually you do that at the start of the application:
...
LogCentral.Current.RequestMoreInformation +=
new LogCentralRequestMoreInformationEventHandler(
Current_RequestMoreInformation );
...
The actual handler Current_RequestMoreInformation
could be defined as following. In this example, the variable this.counter
is some class member that gets modified by certain methods and is logged whenever a severe message is being logged.
private static void Current_RequestMoreInformation(
object sender,
LogCentralRequestMoreInformationEventArgs e )
{
if ( e.Type==LogCentralRequestMoreInformationEventArgs.LogType.Error ||
e.Type==LogCentralRequestMoreInformationEventArgs.LogType.Fatal ||
e.Type==LogCentralRequestMoreInformationEventArgs.LogType.Warn )
{
e.MoreInformationMessage +=
string.Format(
"The current counter value is '{0}'.",
this.counter );
}
}
Now whenever a message is logged, the Current_RequestMoreInformation
handler is being called by the logging class.
Default handlers provided
The LogCentral
class itself uses this event internally to provide context information for the following items:
- HTTP information
When running inside a web context, several information like requested URI, referer URI, user host name, session information etc. is being logged.
- Assembly information
Information about the assemblies of the running application are being logged. These information include assembly path, assembly date, assembly version, etc.
- Environmental information
Information about the local Windows environment is being logged. These information include current logged in user, user domain, machine name, CLR version, current working directory, etc.
- Network information
Information about the host name and the IP addresses of the computer running the application are being logged.
Please note: The default handlers only add the context information if the logged message is of type "warn", "error" or "fatal".
For completeness, there also exists an event that gets raised after a log occurred. You can add your own handler to that event to perform miscellaneous tasks after a message got logged, e.g. cleaning up resources or something similar.
Example
The following code adds a handler for the event. Usually you do that at the start of the application:
...
LogCentral.Current.Log +=
new LogCentralLogEventHandler(
Current_Log );
...
The actual handler Current_Log
could be defined as following. In this example it simply writes a notify message to the Debug
output.
private static void Current_Log(
object sender,
LogCentralLogEventArgs e )
{
System.Diagnostics.Debug.Write( "Log occured." );
}
- Protecting of passwords
Every message that gets logged is being checked for potentially security related keyword like "password" or similar. If such a keyword is detected, a warning message ("...illegal words contained...") is actually being logged instead of the message itself.
- Provide access to the path of the configuration file
By using the ConfigurationFilePath
property, you can read the full path of the current configuration property, e.g. "C:\Inetpub\WwwRoot\MyWebApplication\Web.config". Sometimes this can be useful when you, for example, want to read other files from exactly the same folder where the configuration file is stored.
Using the Code in your Projects
The code download for this article contains a small example project as well as precompiled versions (.NET version 1.1, debug and release) of the library. To use it in your own projects you have (at least) two possibilities:
- Simply copy the content of the "Release" folder (namely the files "log4net.dll" and "ZetaLogger.dll") to a folder of your choice and add a reference to it by using the Add reference command in Visual Studio .NET 2003.
- Copy and add the source file "LogCentral.cs" to your project so that it gets compiled with your project. Please note that you still need to add a reference to the "log4net.dll" library.
I recommend that you first take a look at the example application and then use the library inside your own projects.
Conclusion
In this article, I've shown you a class for adding logging capabilities to your application. I'm using the class since more than two years, slightly enhancing the functionality from time to time. Hopefully you'll find this class useful, too.
For questions, comments and remarks, please use the commenting section at the bottom of this article.
References
- The LOG4NET framework homepage.