65.9K
CodeProject is changing. Read more.
Home

A simple and complete logger for .net applications

starIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIconemptyStarIcon

1.71/5 (5 votes)

Dec 30, 2008

CPOL

1 min read

viewsIcon

18611

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
// 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
// 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)
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

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

Now a few vars that the logger needs to work

// 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,                // Fucking delegates!
    m_delegateCount;                     // Again fucking 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:
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

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:

// 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,                // Fucking delegates!
            m_delegateCount;                     // Again fucking 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