
Introduction
A simple but robust logging class that fulfills the following requirements:
- One simple, static class, easy to understand.
- Works "out of the box", just include it and start logging. No initialization, no configuration necessary. No dependencies.
- Simple, static methods to write a log entry.
- Logs XML fragments to a file, one tag per log entry.
- Easy but flexible way to change file and folder where logs are written to.
- Possibility to pass log severity (info, warning, error, exception).
- Possibility of simple filtering by log severity.
- Log exceptions recursively with all inner exceptions, data, stack trace and specific properties of some exception types.
- Has very little impact on performance of the main program due to queue-and-background task approach.
- Is thread-safe, i.e. several threads can log into the same file concurrently.
- Automatically logs the class and method name where the log method was called from.
- Robust, compact and well-documented.
Background
Some time ago, I was looking for a really simple logging class.
Having a small project with a handful of files and classes, I didn't want to blow it up with a complex framework like log4net with dozens of classes and files. I didn't want to deal with hundreds of options and configuration settings. I didn't want to add a configuration file, just for logging. I didn't want to spend hours for learning how logging works with a particular framework.
I just wanted to include one class as source code and start logging. A class I can understand in minutes, not days, and I can eventually extend to my needs. Simple as that. I thought "why re-inventing the wheel?" there are millions of logging approaches out there!
I stumbled over Marco Manso's Really Simple Log Writer. Yeah, that is really simple! A static class with some logging methods, working "out of the box". Logs XML fragments to a file, one tag per entry. Just what I was looking for!
Well ... logging inner exceptions should be done recursively. No big deal, changed in a minute. Oh, there is some code-redundancy in writing the logs ... no problem, duplicate code extracted in a separate method. Hmm, configuring the log file name and its location could be done in a more consistent, flexible way ... and comments and regions would be a good idea, too. When I'm logging, I'd like to distinguish between info, warning and error. That's really not asked too much, is it? Ok, logging severity built in, along with some shortcut methods, of course. For compactness, IMHO, severity, date and time, should not be logged as separate tags, but as attributes - changed. One thing that I ever wanted and often missed, is that the location in code where a logging method was called is logged automatically. This way, you do not have to write "exception in method xy ..." each time; logging does that for you! Built in quickly. What do people suggest for Marco's class? A method to get the log file completed to a regular XML document? Right, that will be needed for sure. And when we're already about it, it could be shown in the browser with just one call. Great, isn't it? Log filtering is something that is always available in every logging system. Shall this class provide it, too? Yes, but it must be very basic and intuitive. Should not have an impact on the learning curve - built in a simple filtering by severity that everyone will understand instantly. Whew, we're through now, aren't we? What about performance and thread safety? True. We're neither - nor. Good logging systems have a queue and a background task to actually write the entries to disk. That way, they achieve both, performance and thread safety. Hmm, wasn't I looking for a simple class initially? That feature would make the class much (?) more complex. But it's the correct way how to do it! It's the last thing I'm adding.
Now, the class can be used in any project without worrying - and it is still comparibly simple and easy to understand. From my point of view, it should be a good compromise between simplicity and functionality.
Using the code
As Marco nicely expressed it: That's the beauty of it. Just include the class as source code anywhere in your project and start logging:
SimpleLog.Info("Test logging started.");
SimpleLog.Warning("This is a warning.");
SimpleLog.Error("This is an error.");
That will produce the following output in a log file e.g. "2013_04_29.log" in the current working directory:
<LogEntry Date="2013-04-29 18:56:43" Severity="Info"
Source="SimpleLogDemo.Program.Main" ThreadId="9">
<Message>Test logging started.</Message>
</LogEntry>
<LogEntry Date="2013-04-29 18:56:43" Severity="Warning"
Source="SimpleLogDemo.Program.Main" ThreadId="9">
<Message>This is a warning.</Message>
</LogEntry>
<LogEntry Date="2013-04-29 18:56:43" Severity="Warning"
Source="SimpleLogDemo.Program.Main" ThreadId="9">
<Message>This is an error.</Message>
</LogEntry>
Of course, exceptions can be logged, too, including all inner exceptions:
try
{
DoSomething();
}
catch(Exception ex)
{
SimpleLog.Log(ex);
}
...
private static void DoSomething()
{
SimpleLog.Info("Entering method. See Source which method is meant.");
try
{
DoSomethingElse(null);
}
catch(Exception ex)
{
throw new InvalidOperationException("Something went wrong.", ex);
}
}
private static void DoSomethingElse(string fred)
{
SimpleLog.Info("Entering method. See Source which method is meant.");
try
{
int a = fred.IndexOf("Hello");
}
catch(Exception ex)
{
throw new Exception("Something went wrong.", ex);
}
}
That will produce the following output:
<LogEntry Date="2013-04-29 18:56:43" Severity="Info"
Source="SimpleLogDemo.Program.DoSomething" ThreadId="9">
<Message>Entering method. See Source which method is meant.</Message>
</LogEntry>
<LogEntry Date="2013-04-29 18:56:43" Severity="Info"
Source="SimpleLogDemo.Program.DoSomethingElse" ThreadId="9">
<Message>Entering method. See Source which method is meant.</Message>
</LogEntry>
<LogEntry Date="2013-04-29 18:56:43" Severity="Exception"
Source="SimpleLogDemo.Program.Main" ThreadId="9">
<Exception Type="System.InvalidOperationException" Source="SimpleLogDemo.Program.DoSomething">
<Message>Something went wrong.</Message>
<Exception Type="System.Exception" Source="SimpleLogDemo.Program.DoSomethingElse">
<Message>Something went wrong.</Message>
<Exception Type="System.NullReferenceException" Source="SimpleLogDemo.Program.DoSomethingElse">
<Message>Object reference not set to an instance of an object.</Message>
<StackTrace> at SimpleLogDemo.Program.DoSomethingElse(String fred)
in D:\Projekt\VisualStudio\SimpleLogDemo\SimpleLogDemo\Program.cs:line 91</StackTrace>
</Exception>
</Exception>
</Exception>
</LogEntry>
The log file that is written to is assembled as follows:
public static string FileName
{
get
{
return GetFileName(DateTime.Now);
}
}
public static string GetFileName(DateTime dateTime)
{
return string.Format("{0}\\{1}{2}{3}.{4}", LogDir, Prefix,
dateTime.ToString(DateFormat), Suffix, Extension);
}
Want to use another log file at another location? Apart from setting several properties like prefix, suffix, extension, directory, log level, etc. separately, you can use convenience method SetLogFile to set them all at once:
public static void SetLogFile(string logDir = null, string prefix = null,
string dateFormat = null, string suffix = null,
string extension = null, Severity? logLevel = null)
For example, you can say:
SimpleLog.SetLogFile(".\\Log", "MyLog_");
To get a log file as complete XML, you can use:
public static XDocument GetLogFile()
{
return GetLogFile(DateTime.Now);
}
You can also show the current log file as XML in a browser with just one line:
public static void ShowLogFile()
That's it! You don't have to care about anything else. Enjoy!