Implement a simple logger in C#

Implement a custom logging framework to log your application's data to predefined log targets

Implement a simple logger in C#

Logger

You may want to log information such as, the sequence of events or errors when they occur in an application. To do this, you would typically design and develop a logging framework. There are many logging frameworks around that you can take advantage of, but this article discusses how you can design and develop your own logging framework with ease. In this article I would present a discussion on how we can implement a simple logger in C#.

Log target

First off, you would need to understand the log targets -- these imply the various types of targets where the data would be logged. Let’s assume that we would log data to flat files, a database, and the event log. The following enumeration defines the log targets we would use in this simple framework.

public enum LogTarget

    {

        File, Database, EventLog

    }

The logger classes

The next step is to design and implement the classes. We would have three distinct classes -- namely, FileLogger, DBLogger, and EventLogger -- to log data to file, database, and the event log respectively. All of these classes should inherit the abstract base class named LogBase. Here’s how these classes are organized.

    public abstract class LogBase

    {

        public abstract void Log(string message);

    }

    public class FileLogger : LogBase

     {

        public string filePath = @"D:\IDGLog.txt";

        public override void Log(string message)

        {

            using (StreamWriter streamWriter = new StreamWriter(filePath))

            {

                streamWriter.WriteLine(message);

                streamWriter.Close();

            }           

        }

    }

public class DBLogger : LogBase

    {

        string connectionString = string.Empty;

        public override void Log(string message)

        {

            //Code to log data to the database

        }

    }

    public class EventLogger: LogBase

    {

        public override void Log(string message)

        {

            EventLog eventLog = new EventLog("");

            eventLog.Source = "IDGEventLog";

            eventLog.WriteEntry(message);

        }

    }                                

I have kept the DBLogger class incomplete intentionally. I leave it to the readers to fill in the appropriate code so as to log messages to the database.

As you can see, all the three classes FileLogger, EventLogger, and DBLogger extend the abstract base class LogBase. The abstract base class LogBase declares the abstract method called Log. The Log() method accepts a string as the parameter; this is the text data that needs to be logged to a file or a database or to the event log. 

The LogHelper Class

Let's now create a helper class that can be used to invoke the respective logger based on the parameter passed. This helper class will be used to simplify the calls to the Log() method of the respective logger classes. The following code snippet shows how this helper class looks like.

public static class LogHelper

    {

        private static LogBase logger = null;

        public static void Log(LogTarget target, string message)

        {

            switch(target)

            {

                case LogTarget.File:

                    logger = new FileLogger();

                    logger.Log(message);

                    break;

                case LogTarget.Database:

                    logger = new DBLogger();

                    logger.Log(message);

                    break;

                case LogTarget.EventLog:

                    logger = new EventLogger();

                    logger.Log(message);

                    break;

                default:

                    return;

            }

        }

    }

The Log() method of the LogHelper class accepts a string and an instance of the LogTarget enumeration as parameters. It then uses a switch: case construct to determine the target to which the text message needs to be logged.

Synchronizing calls to the Log() method

Oops! We missed out synchronizing calls to the respective Log() methods. To do this, we need to use the lock keyword in each of the Log() methods of the respective logger classes and incorporate appropriate code to synchronize the Log() methods. Refer to the LogBase class given next. We have incorporated a protected member that would be used to apply the lock in the Log() method of each of the derived classes. Here’s how the modified versions of these classes would look like.

public abstract class LogBase

    {

        protected readonly object lockObj = new object();

        public abstract void Log(string message);

    }

    public class FileLogger : LogBase

    {

        public string filePath = @"D:\IDGLog.txt";

        public override void Log(string message)

        {

            lock (lockObj)

            {

                using (StreamWriter streamWriter = new StreamWriter(filePath))

                {

                    streamWriter.WriteLine(message);

                    streamWriter.Close();

                }

            }

        }

    }

    public class EventLogger : LogBase

    {

        public override void Log(string message)

        {

            lock (lockObj)

            {

                EventLog m_EventLog = new EventLog("");

                m_EventLog.Source = "IDGEventLog";

                m_EventLog.WriteEntry(message);

            }

        }

    }

    public class DBLogger : LogBase

    {

        string connectionString = string.Empty;

        public override void Log(string message)

        {

            lock (lockObj)

            {

                //Code to log data to the database

            }

        }

    }

You can now call the Log() method of the LogHelper class and pass the log target and the text message to log as parameters.

class Program

    {

        static void Main(string[] args)

        {

            LogHelper.Log(LogTarget.File, "Hello");

        }

    }

If you need to log the text message to a different log target, you would simply pass the appropriate log target as a parameter to the Log() method of the LogHelper class.

There are many ways in which you can improve this logging framework. You can implement asynchrony so that the messages that would be logged asynchronously. You can also implement a queue so that when a large number of messages arrive, they are queued up and the logger can process each of these messages asynchronously without having to block the current thread. Besides, you may want to implement message criticality levels, such as informational messages, error messages, and so on.

This article is published as part of the IDG Contributor Network. Want to Join?

To comment on this article and other InfoWorld content, visit InfoWorld's LinkedIn page, Facebook page and Twitter stream.
From CIO: 8 Free Online Courses to Grow Your Tech Skills
Notice to our Readers
We're now using social media to take your comments and feedback. Learn more about this here.