How often do you start a new application and get part of the way through and then an error occurs and you don't immediately know why? You wish you had implemented some error logging, but now you dread having to configure something like Microsoft Enterprise Library or some other heavy logging application.
Introducing Simple Logger
Simple Logger just needs a couple simple entries in the appSettings section of the web.config or app.config and a reference to this library and your done. It comes built in with a flexible way of logging to one or more destinations easily.
This is an example of the code you'd have to write in the code-behind of a button click event handler.
protected void btnCreateError_Click(object sender, EventArgs e)
{
try
{
throw new Exception("This is an exception Thrown by King Wilder.");
}
catch (Exception ex)
{
// This single line will log to the destination of your choice.
LogManager.WriteMessage(new LogEventArgs("SimpleLogger.Web._Default.btnCreateError_Click", ex.Message, ex));
lblMessage.Text = ex.Message;
GridView1.DataBind();
}
}
This single line of code:
LogManager.WriteMessage(new LogEventArgs("SimpleLogger.Web._Default.btnCreateError_Click", ex.Message, ex));
... is all you need to write to log a message to a text file, to an email, to a database, or any other destination.
Quick Install
Three easy steps to add logging to your application.
1. Add a reference to the SimpleLogger assembly to your application.

2. Add at least one LogType to the appSettings section of the web.config or app.config file. If you are assigning a logging type such as EmailLogger or SqlLogger, then you'll need to add additional configuration.

3. Simply make a reference to the Class where you want to handle the error, such as in the Click event handler of a button.
protected void btnCreateError_Click(object sender, EventArgs e)
{
try
{
throw new Exception("This is an exception Thrown by King Wilder.");
}
catch (Exception ex)
{
// This single line will log to the destination of your choice.
LogManager.WriteMessage(
new LogEventArgs(
"SimpleLogger.Web._Default.btnCreateError_Click",
ex.Message, ex));
}
}
There isn't a ton of Xml configuration you need to get this to work. Here's an example of the appSettings section to set two different types of loggers to execute when you call this.

So let's take a look at the Visual Studio 2008 solution.
Simple Class Library
This project is available on the download page. This solution has three different projects:
- Simple Logger - the logger class library
- Unit tests - some very simple unit tests
- Sample Web Application - a sample web app that implements Simple Logger

This is just to help demonstrate how flexible and easy to use this class library really is.
This is the app.config for the Unit Tests. It shows that I am implementing two different Loggers, the FileTrace logger and the Email logger.

Obviously, in order to use the Email logger, you must have the system.net/mailSettings sections added to the config file.
The web.config file for the sample web app implements two different loggers, the FileError logger and the Sql logger which logs messages to a database table.

Use Design Patterns
In order to make it easy to refactor and extend, the Simple Logger makes use of couple different design patterns, Factory and Strategy Patterns. And I wrap it all up using encapsulation in a static class so it's easy to implement. For more information on Design Patterns, go to the Gang of Four web site, or the many other resources by going to Google.
The structure of the library is pretty simple, whenever you need to add a new logger to the library, just have it implement ILogger and add any code that appropriate for that class.
This is the layout of the class project. You can see that the "factory" classes are all in the "Factory" folder. The actual logging classes that do the work are located in the root of that project, the EmailLogger.cs file, the FileLogger.cs file, and the SqlLogger.cs file.

You can create your own Logger classes and save them here, along side the classes already created. Then recompile and away you go.
A Quick Overvew of the Factory classes
At the root of the whole thing is the ILogger interface. It contains a single signature called Log.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SimpleLogger
{
public interface ILogger
{
void Log(LogEventArgs args);
}
}
It takes a LogEventArgs class as an argument which passes along various pieces of data. I borrowed the LogEventArgs class as a parameter from the Patterns In Action implementation from the folks at DoFactory. They are the guys behind much of what's happening with Design Patterns today.
Here's a look at the LogEventArgs class.
using System;
namespace SimpleLogger
{
public class LogEventArgs
{
private Exception _exception;
private string _message;
private string _source;
private string _logType;
public LogEventArgs(string source, string message, Exception ex)
{
this._exception = ex;
this._message = message;
this._source = source;
}
public string LogType
{
get { return _logType; }
set { _logType = value; }
}
public string Source
{
get { return _source; }
set { _source = value; }
}
public string Message
{
get { return _message; }
set { _message = value; }
}
public Exception Exception
{
get { return _exception; }
set { _exception = value; }
}
}
}
The constructor takes three parameters, Source, Message, and the Exception.
- Source - this is a helpful identifier of where the exception occurred. It should be something that quickly helps you figure out where the error originated.
- Message - this is the exception message.
- Exception - this is the Exception object itself. The factory and the subclasses will be able to extract detailed information about the exception from this object.
You probably have noticed the LogType property. This is set by the factory to help subclasses identify which logtype was set, as defined in the appSettings section of the config file.
LogManager Static Class
The LogManager static class is the one that you implement in your code. As shown above, it's a single line of code that takes a single argument to store information about the exception.

The WriteMessage method initiates the entire process and by maintaining a loosely-coupled architecture, the WriteMessage method doesn't know anything about which Loggers to instantiate, nor does it care.
It first gets a list of LogTypes from the config file, then splits them on the commas. Then it converts the string types to the LoggingStrategy enum type. Then it creates the LogFactory object and calls the CreateLoggers. It passes in the list of LoggingStrategy types which uses the Strategy Pattern to decide which logger to create.
Here's a look at the enum LoggingStrategy.
namespace SimpleLogger
{
enum LoggingStrategy
{
FileError,
FileTrace,
Sql,
Event,
Email
}
}
And here's a look at the LogFactory class.

When this object if first instantiated, the DefineStrategies is called to load all the concrete Logger Subclasses in a list. Then the CreateLoggers method loops through the list of LoggingStrategy enum types to instantiate the appropriate subclass, such as the FileLogger, or the EmailLogger.
Funny story!
A quick little aside for a moment. I started building this application because I wanted something I could use over and over and integrate immedately without any hassle. I got to this class, LogFactory, and originally had a switch statement, which isn't all that bad, but I figured there had to be a better way.
A year ago I didn't know what a Design Pattern was, and someone said I should read the Head First Design Patterns book to learn how to build applications that will last and be able to grow. A whole new world opened up for me when I finished that book.
But on this problem, I couldn't really find the answer in that book or in other resources on the Net until I stumbled upon DimeCasts.net. I saw that there was a video on the Strategy Pattern and I watched. To my amazement, Derik Whittaker was demoing exactly what I needed, using a similar logger as an example.
His solution was what I needed to solve my problem to make my Factory class easy to extend. So special thanks to Derik Whittaker on his Strategy Pattern video.
Now back to the project...
Then it addes the subclass type to a list of ILogger types and returns that to the WriteMessage method. This loops through the list of ILogger types and calls the log method which processes the request.

The line that reads, args.LogType = types[i];, assigns the log type from the appSettings settings to the logger subclass. This helps build it's message with the type of logger it is. You can see an example of how it works in the FileLogger class.
The FileLogger Subclass
We'll take a quick look at some of the subclasses that implement ILogger to see how each is used.
The FileLogger class simply write a message to a text file on a Logs folder of the application. Here's what the FileLogger class looks like.
using System;
using System.IO;
namespace SimpleLogger
{
public class FileLogger : ILogger
{
#region ctors
public FileLogger()
{
}
#endregion
#region ILogger Members
public void Log(LogEventArgs args)
{
string _fileName;
_fileName = AppDomain.CurrentDomain.BaseDirectory + @"\Logs\" + args.LogType + "s-" + string.Format("{0:yyyy-MM-dd}", DateTime.Now) + ".txt";
string msg = "[" + DateTime.Now.ToString() + "] Source: " + args.Source + " - Message: " + args.Message;
if (args.LogType == "FileError") msg += " - StackTrace: " + args.Exception.StackTrace + " - Target Site: " + args.Exception.TargetSite.ToString();
FileStream fs;
try
{
fs = new FileStream(_fileName, FileMode.Append);
}
catch (DirectoryNotFoundException)
{
Directory.CreateDirectory((new FileInfo(_fileName)).DirectoryName);
fs = new FileStream(_fileName, FileMode.Append);
}
StreamWriter sw = new StreamWriter(fs);
try
{
sw.WriteLine(msg);
}
catch
{
}
finally
{
try
{
sw.Close();
}
catch
{
}
}
}
#endregion
}
}
You can see that if first implements the ILogger interface which creates the Log method. This is how you can create your own logger classes by creating a class that implements ILogger and writing whatever code you need to handle processing the message.
This class simply builds a file path to the Logs folder, creates a file name with the LogType and Date and then creates the message that will be written to the file stream.
It then creates a FileStream object and writes (appends) the message to the text file. That's it! Quick and easy.
Now let's take a look at the EmailLogger class.
EmailLogger subclass
The EmailLogger subclass also implements the ILogger interface but it sends an email to whoever you want. Let's take a look at how it's used.
using System;
namespace SimpleLogger
{
class EmailLogger : ILogger
{
#region ILogger Members
public void Log(LogEventArgs args)
{
SimpleLogger.Email.Email email = new SimpleLogger.Email.Email();
// these email addresses should come from the appSettings or database.
email.ToList = "someone@youremail.com";
email.FromEmail = "info@myemail.com";
email.isHTML = false;
email.Subject = "A test email from the EmailLogger class.";
email.MessageBody = "This is a test email from the EmailLogger Class and it is used from the LoggerTests.";
try
{
email.SendEmail(email);
}
catch (Exception)
{
}
}
#endregion
}
}
You can see it creates an Email object which will handle the process of sending the mail. Look how clean this class is. Of course the data that would populate the properties of the Email object would most likely come from a database or some settings file.
Lastly, we'll look at the SqlLogger that updates a database table with the exception information.
SqlLogger subclass
The SqlLogger class is one that might be used the most. I simply grabs information from the LogEventArgs class and passes the data to the SQL text, and then the data is inserted into the ErrorLog table. Here's what the class looks like.
using System;
using System.Configuration;
using System.Data.Common;
namespace SimpleLogger
{
public class SqlLogger : ILogger
{
public void Log(LogEventArgs args)
{
string connName = ConfigurationManager.AppSettings.Get("connName");
string connection = ConfigurationManager.ConnectionStrings[connName].ConnectionString;
// Database routines can go here.
DbProviderFactory df = DbProviderFactories.GetFactory("System.Data.SqlClient");
DbConnection cn = df.CreateConnection();
cn.ConnectionString = connection;
cn.Open();
string cmdText = "INSERT INTO ErrorLog (Date, MethodName, ErrorMessage, StackTrace, Source, TargetSite)";
cmdText += " VALUES ('" + DateTime.Now.ToString() + "','" + args.Source + "','" + args.Message + "','" + args.Exception.StackTrace + "','";
cmdText += args.Exception.Source + "','" + args.Exception.TargetSite.ToString() + "')";
DbCommand cmd = df.CreateCommand();
cmd.Connection = cn;
cmd.CommandText = cmdText;
cmd.ExecuteNonQuery();
cmd.Dispose();
cn.Close();
}
}
}
Here's another look at the web.config sections that help this class.

You'll notice that there are two LogTypes listed, FileError and Sql. So both of these loggers will execute when the LogManager.WriteMessage() method is called.
Another key of the appSettings section is for the connection string to the database. It sets a name of the connection string to use, in this case the ErrorLogConnection. This information is used at the beginning of the Log method to get the connection string to the database.
From there, code is used to build the Insert statement and make a connection to the database and execute the command.
Summary
The SimpleLogger library was built to help me add quick and simple logging functionality to an application I'm building that is too small for something like Enterprise Library, etc.
It's built to allow you to easily add your own subclasses if you need to log to the Event logs, or the registry or some other repository.
Again, you can download this complete solution from the Downloads page.
I hope you find this useful and drop me a note if you have any questions.
Thank you.