Click here to Skip to main content
15,891,673 members
Articles / Programming Languages / C#

A simple and complete logger for .net applications

Rate me:
Please Sign up or sign in to vote.
1.71/5 (5 votes)
30 Dec 2008CPOL1 min read 18.3K   21   5
The very complete logger for .net

Introduction

Here you got a way to make a very good logger for your .NET applications.

Background

I wanted to make a logger with almost all functionality, I mean, getting line numbers, methods, thread ids, etc.

Usage Example

First must call the Init method
C#
// Here is the method signature
public void Init(LogLevel minLevel, string dirPath, string appId, int closeFileMinutes,
    long maxFileSize_KB, bool overwriteLogFile)

//Example:
CLog.Instance.Init(LogLevel.NOTICE, "." /*current dir*/,
    "myAPpId", 1440 /*a day*/, 4096 /*4 MB */, false);
Now we can send anything to the logger
C#
// Here are the methods signatures
public void Log(LogLevel logLevel, params string[] logData)
public void Log(int stackLevel, LogLevel logLevel, params string[] logData)

//Example:
catch (Exception ex){
    CLog.Instance.Log(LogLevel.ERROR, "Error logging in", ex.ToString());
}
If you want to create another logger just add a delegate to the SendLog event, and do wherever you want inside it (I am not using it, but it is there)
C#
public delegate void DoLog(int stackLevel, LogLevel logLevel, params string[] logData);

public event DoLog SendLog{add;remove}

The Code

The definition of the log levels, very simple

C#
public enum LogLevel
{
    DEBUG = 0,
    NOTICE,
    WARNING,
    ERROR,
    FATAL,
}

Now a few vars that the logger needs to work

C#
// CLog is a Singleton
public static readonly CLog Instance = new CLog();

private LogLevel m_minLevel;        // The min level to be logged

private object m_lockLog;           // Critical section

private StreamWriter m_logStream;   // Log stream for writing

private long
    m_maxFileSize,        // Max size in bytes for file
    m_currentFileSize;    // Current file size

private System.Threading.Timer m_closeTimer;     // Timer for reset on time out

private int
    m_closeFileTime,                     // Time to reset (milliseconds)
    m_delegateStackLevel,                // F***ing delegates!
    m_delegateCount;                     // Again f***ing delegates

private bool m_overwriteLogFile;         // If false, filename has the created date
                                         // time on his name, if true, the name is
                                         // always the app id

private string
    m_logDirectory,    // Log directory
    m_appId,           // Application Id
    m_logFileName;     // Full path of the log file

private DoLog m_sendLog;    // Broadcast to all listening loggers 
                            // (implementing DoLog delegate)

NOTE: Something curious is that if I add a listener for the delegate, CLR uses one stack to call the delegate. But if I add more delegates, it takes 2 stacks to calls that method, I didnt find why. That is why I used the variables m_delegateStackLevel and m_delegateCount

The Init method:
C#
public void Init(LogLevel minLevel, string dirPath, string appId,
    int closeFileMinutes, long maxFileSize_KB, bool overwriteLogFile) {
    lock (m_lockLog) {
        if (m_logStream == null) {
            m_overwriteLogFile = overwriteLogFile;
            m_minLevel = minLevel;
            m_appId = appId;
            m_closeFileTime = closeFileMinutes * 60000;
            m_maxFileSize = maxFileSize_KB * 1024;
            try {
                if (dirPath == String.Empty)
                    dirPath = ".";
                if (!Directory.Exists(dirPath))
                    Directory.CreateDirectory(dirPath);

                DirectoryInfo dirInfo = new DirectoryInfo(dirPath);

                m_logDirectory = dirInfo.FullName;

                m_logFileName = Path.Combine(m_logDirectory,
                                         String.Concat("Log_", m_appId,
                                         m_overwriteLogFile ? String.Empty : 
                                         DateTime.Now.ToString("yyyy-MM-dd_HH.mm.ss"),
                                         ".log"));
                
                m_logStream = new StreamWriter(m_logFileName,
                                         false, Encoding.ASCII);
                m_logStream.AutoFlush = true;

                m_closeTimer = new System.Threading.Timer(
                                         new TimerCallback(newFile), null,
                                         m_closeFileTime, m_closeFileTime);
                Log(2, LogLevel.NOTICE, "Log started,
                                         min log level is: " + minLevel);
            }
            catch { }
        }
    }
}

The Log main method

C#
public void Log(int stackLevel, LogLevel logLevel, params string[] logData) {
    if (logLevel >= m_minLevel) {  // Maybe i had to lock the critical section here
        try {
            StackFrame sf = new StackFrame(stackLevel, true);
            string fileName = sf.GetFileName();
            StringBuilder logBuilder = new StringBuilder(
                                DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + ";"
            + logLevel + ";"
            + "Thread:" + Thread.CurrentThread.ManagedThreadId + ";"
            + sf.GetMethod() + ";"
                        // Never fails, if it cannot get filename, will 
                        // return -1
                  + fileName.Substring(
                               fileName.LastIndexOf(Path.DirectorySeparatorChar) + 1) + ";"  
            + "Line:" + sf.GetFileLineNumber()
            ,1024);   // I think that will be enough

            foreach (string data in logData) {
                logBuilder.Append(";" + data);
            }
            // Locking as minimum as i can
            lock (m_lockLog) {
#if(DEBUG)
                Debug.WriteLine(logBuilder);
#endif
                m_logStream.WriteLine(logBuilder);

                     m_currentFileSize += Encoding.ASCII.GetByteCount(
                                    logBuilder.ToString());  // Best way to handle file size

                if (m_currentFileSize > m_maxFileSize)
                    m_closeTimer.Change(0,
                                              m_closeFileTime); // Max file size exceeded,
                                                                // reset file immediately
                if (m_sendLog != null) {
                    m_sendLog(stackLevel + m_delegateStackLevel,
                                                  logLevel, logData);
                }
            }
        }
        catch { }  // Only fails if it is called before Init(),
                              // or if someone misses up the things
    }
}

And now the complete class code:

C#
// Logger.cs
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;

/*************************************************************************
 * Created by Juan Ramirez (ichr@mm)
 * juanantonio.ram@gmail.com
 * 
 * You can do wherever you want with that copy of the program, but just
 * keep this lines on it, it is better than stealing all the credit for your self 
 * 
 * ***********************************************************************/


namespace Logger
{
    public enum LogLevel
    {
        DEBUG = 0,
        NOTICE,
        WARNING,
        ERROR,
        FATAL,
    }

    public delegate void DoLog(int stackLevel, LogLevel logLevel,
        params string[] logData);

    public sealed class CLog // Singleton
    {
        public static readonly CLog Instance = new CLog();

        private CLog() {
            m_lockLog = new object();
            m_currentFileSize = 0;
            m_sendLog = null;
            m_delegateStackLevel = 1;
        }


        #region member vars

        private LogLevel m_minLevel;

        private object m_lockLog;           // Critical section

        private StreamWriter m_logStream;   // Log stream for writing

        private long
            m_maxFileSize,        // Max size in bytes for file
            m_currentFileSize;    // Current file size

        private System.Threading.Timer m_closeTimer;     // Timer for reset on time out

        private int
            m_closeFileTime,                     // Time to reset (milliseconds)
            m_delegateStackLevel,                // F***ing delegates!
            m_delegateCount;                     // Again f***ing delegates
        
        private bool m_overwriteLogFile;         // If false, filename has the
                                                 // created date time on his name,
                                                 // if true, the name is always the app id

        private string
            m_logDirectory,    // Log directory
            m_appId,           // Application Id
            m_logFileName;     // Full path of the log file

        private DoLog m_sendLog;    // Broadcast to all listening
                                    // loggers (implementing DoLog delegate)

        #endregion

        #region Props

        public event DoLog SendLog {
            add {
                lock (m_lockLog) {
                    m_sendLog += value;
                    m_delegateCount += 1;
                    if (m_delegateCount == 2) {
                        m_delegateStackLevel = 2;
                    }
                }
            }
            remove {
                lock (m_lockLog) {
                    m_sendLog -= value;
                    m_delegateCount -= 1;
                    if (m_delegateCount == 1) {
                        m_delegateStackLevel = 1;
                    }
                }
            }
        }

        public string LogFileName {
            get {
                lock (m_lockLog) {
                    return m_logFileName;
                }
            }
        }

        public LogLevel MinLevel {
            get {
                return m_minLevel;
            }
            set {
                m_minLevel = value;
            }
        }

        #endregion


        public void Init(LogLevel minLevel, string dirPath, string appId,
            int closeFileMinutes, long maxFileSize_KB, bool overwriteLogFile) {
            lock (m_lockLog) {
                if (m_logStream == null) {
                    m_overwriteLogFile = overwriteLogFile;
                    m_minLevel = minLevel;
                    m_appId = appId;
                    m_closeFileTime = closeFileMinutes * 60000;
                    m_maxFileSize = maxFileSize_KB * 1024;
                    try {
                        if (dirPath == String.Empty)
                            dirPath = ".";
                        if (!Directory.Exists(dirPath))
                            Directory.CreateDirectory(dirPath);

                        DirectoryInfo dirInfo = new DirectoryInfo(dirPath);

                        m_logDirectory = dirInfo.FullName;

                        m_logFileName = Path.Combine(m_logDirectory,
                            String.Concat("Log_", m_appId,
                            m_overwriteLogFile ? String.Empty : DateTime.Now.ToString(
                            "yyyy-MM-dd_HH.mm.ss"), ".log"));
                        
                        m_logStream = new StreamWriter(m_logFileName, false,
                            Encoding.ASCII);
                        m_logStream.AutoFlush = true;

                        m_closeTimer = new System.Threading.Timer(new TimerCallback(
                            newFile), null, m_closeFileTime, m_closeFileTime);
                        Log(2, LogLevel.NOTICE, "Log started, min log level is: " +
                            minLevel);
                    }
                    catch { }
                }
            }
        }


        public void Log(LogLevel logLevel, params string[] logData) {
            Log(2, logLevel, logData);
        }


        public void Log(int stackLevel, LogLevel logLevel, params string[] logData) {
            if (logLevel >= m_minLevel) {  // Maybe i had to lock the critical section here
                try {
                    StackFrame sf = new StackFrame(stackLevel, true);
                    string fileName = sf.GetFileName();
                    StringBuilder logBuilder = new StringBuilder(
                                                    DateTime.Now.ToString(
                                                        "yyyy-MM-dd HH:mm:ss.fff") + ";"
                                                    + logLevel + ";"
                                                    + "Thread:" +
                                                        Thread.CurrentThread.ManagedThreadId + ";"
                                                    + sf.GetMethod() + ";"
                                                    // Never fails, if it cannot get filename,
                                                    // will return -1
                                                      + fileName.Substring(
                                                         fileName.LastIndexOf(
                                                         Path.DirectorySeparatorChar) + 1) + ";"  
                                                    + "Line:" + sf.GetFileLineNumber()
                                                ,
                                                1024);   // I think that will be enough

                    foreach (string data in logData) {
                        logBuilder.Append(";" + data);
                    }
                    // Locking as minimum as i can
                    lock (m_lockLog) {
#if(DEBUG)
                        Debug.WriteLine(logBuilder);
#endif
                        m_logStream.WriteLine(logBuilder);

                        m_currentFileSize += Encoding.ASCII.GetByteCount(
                            logBuilder.ToString());  // Best way to handle file size

                        if (m_currentFileSize > m_maxFileSize)
                            m_closeTimer.Change(0, m_closeFileTime); // Max file size
                            // exceeded, reset file immediately                          

                        if (m_sendLog != null) {
                            m_sendLog(stackLevel + m_delegateStackLevel, logLevel, logData);
                        }
                    }
                }
                catch { }  // Only fails if it is called before Init(), or if someone
                           // misses up the things
            }
        }


        private void newFile(object o) {
            lock (m_lockLog) {
                try {
                    m_logStream.Close();

                    if (m_overwriteLogFile == false) {
                        m_logFileName = Path.Combine(m_logDirectory, string.Concat(
                            "Log_", m_appId, DateTime.Now.ToString(
                            "yyyy-MM-dd_HH.mm.ss"), ".log"));
                    }
                    m_currentFileSize = 0;
                    m_logStream = new StreamWriter(m_logFileName);
                    m_logStream.AutoFlush = true;
                }
                catch { }
            }
        }
    }
}

Notes

This class has been developed taking care about performance, that is why tried to get a lot of info with less work, an example is the way I did to get the file size by the size of the string being writes.

Any suggestion, constructive critics, please, no doubt.

Regards

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



Comments and Discussions

 
GeneralMy vote of 1 Pin
andykernahan31-Dec-08 8:06
andykernahan31-Dec-08 8:06 
GeneralMy vote of 1 Pin
Todd Smith31-Dec-08 7:36
Todd Smith31-Dec-08 7:36 
GeneralBetter options Pin
Not Active30-Dec-08 18:06
mentorNot Active30-Dec-08 18:06 
GeneralSuch as TracerX Pin
MarkLTX31-Dec-08 6:03
MarkLTX31-Dec-08 6:03 
GeneralRe: Such as TracerX Pin
Not Active31-Dec-08 6:20
mentorNot Active31-Dec-08 6:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.