Click here to Skip to main content
15,887,315 members
Articles / Programming Languages / C#
Article

EZLogger - drop-dead easy logging

Rate me:
Please Sign up or sign in to vote.
4.65/5 (20 votes)
22 Mar 2007CPOL2 min read 84.3K   1.8K   94   25
A lightweight .NET logging component.

Introduction

EZLogger is an object that provides basic logging facilities to your application. EZLogger is a lightweight alternative to more feature-rich logging subsystems such as Log4Net and XQuiSoft Logging, both of which are excellent logging mechanisms. I wrote EZLogger because my application's logging needs were simple and I wanted to keep my app's footprint small. I hope you find EZLogger useful and welcome your suggestions.

Features

EZLogger writes log messages to a text file. The log file can be appended to, or a new one created when EZLogger is initialized. Log messages include a timestamp, severity and message text. Logging can be paused and resumed at any time. EZLogger's logging level (i.e. severities of interest) can be changed at run time to filter out unwanted messages. EZLogger is thread safe and can be used to log the activity of multiple threads.

EZLogger supports any combination of these severity levels:
  • Debug - trace and debug messages
  • Info - informational messages
  • Success - success messages
  • Warning - warnings
  • Error - error messages
  • Fatal - fatal errors
  • All - all messages

How to use EZLogger

You use EZLogger by creating a new instance, starting the logger, and calling one of the various logging methods. When you're done using the logger, you stop it as shown below.
C#
// Create and start the logger
uint logLevels = (uint) EZLogger.Level.All;
EZLogger logger =
  new EZLogger ("C:\\EZLoggerTester.log",  // log filename
                false,                     // don't append
                logLevels);                // log levels of interest
bool bStatus = logger.Start();

// Write log messages
logger.Info ("An informational message");
logger.Warning ("A warning");
logger.Fatal ("A fatal error has occured");
...

// Stop logging
logger.Stop();

This code fragment produces the following log file.

12/4/2006 4:38:26 PM  I: An informational message
12/4/2006 4:38:26 PM  W: A warning
12/4/2006 4:38:26 PM  F: A fatal error has occur

The Pause() and Resume() methods can be used to temporarily suspend and restart logging.

C#
// Starting long, boring operation
logger.Pause();
performLongBoringOperation();
logger.Resume();

The Levels property can be set to restrict logging to message severities of interest.

C#
// Only interested in errors and debug msgs
logger.Levels = (uint) (EZLogger.Level.Error | EZLogger.Level.Debug);
logger.Info ("...");   // won't be logged
logger.Error ("...");  // will be logged

How it works

Starting EZLogger causes it to maintain a reference to a StreamWriter. Calls to log messages invoke StreamWriter.WriteLine() which causes formatted text to be written (and flushed) to the log file. Messages are filtered based on the object's LogLevel property. EZLogger's methods are synchronized by marking their bodies as critical sections (using the lock keyword).

Revision History

  • 22 Mar 2007
    Added GetMessageCount() method to retrieve the number of messages logged. Thanks to John Tibbits for the suggestion!
  • 5 Dec 2006
    Added explicit Resume() method instead of overloading the behavior of Start().
    Added ability to filter on arbitrary severity levels.
  • 4 Dec 2006
    Initial version.

License

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


Written By
Technical Lead
Canada Canada
Ravi Bhavnani is an ardent fan of Microsoft technologies who loves building Windows apps, especially PIMs, system utilities, and things that go bump on the Internet. During his career, Ravi has developed expert systems, desktop imaging apps, marketing automation software, EDA tools, a platform to help people find, analyze and understand information, trading software for institutional investors and advanced data visualization solutions. He currently works for a company that provides enterprise workforce management solutions to large clients.

His interests include the .NET framework, reasoning systems, financial analysis and algorithmic trading, NLP, HCI and UI design. Ravi holds a BS in Physics and Math and an MS in Computer Science and was a Microsoft MVP (C++ and C# in 2006 and 2007). He is also the co-inventor of 3 patents on software security and generating data visualization dashboards. His claim to fame is that he crafted CodeProject's "joke" forum post icon.

Ravi's biggest fear is that one day he might actually get a life, although the chances of that happening seem extremely remote.

Comments and Discussions

 
Questionmodify version - auto generate log filename by today date Pin
woongs.bang13-Feb-13 19:57
woongs.bang13-Feb-13 19:57 
XML
using System;
using System.IO;

namespace RavSoft
{
  /// <summary>
  /// An object that provides basic logging capabilities.
  /// Copyright (c) 2006 Ravi Bhavnani, ravib@ravib.com
  ///
  /// This software may be freely used in any product or
  /// work, provided this copyright notice is maintained.
  /// To help ensure a single point of release, please
  /// email and bug reports, flames and suggestions to
  /// ravib@ravib.com.
  ///
  /// -- modify by shinjijoa@empal.com
  /// -- auto generate logfile name by today date
  /// </summary>
  class EZLogger
  {
    #region Attributes

      /// <summary>
      /// Log levels.
      /// </summary>
      public enum Level
      {
        /// <summary>Log debug messages.</summary>
        Debug = 1,

        /// <summary>Log informational messages.</summary>
        Info = 2,

        /// <summary>Log success messages.</summary>
        Success = 4,

        /// <summary>Log warning messages.</summary>
        Warning = 8,

        /// <summary>Log error messages.</summary>
        Error = 16,

        /// <summary>Log fatal errors.</summary>
        Fatal = 32,

        /// <summary>Log all messages.</summary>
        All = 0xFFFF,
      }

      /// <summary>
      /// The logger's state.
      /// </summary>
      public enum State
      {
        /// <summary>The logger is stopped.</summary>
        Stopped = 0,

        /// <summary>The logger has been started.</summary>
        Running,

        /// <summary>The logger is paused.</summary>
        Paused,
      }

    #endregion

    #region Construction/destruction

      /// <summary>
      /// Constructs a EZLogger.
      /// </summary>
      /// <param name="logFilename">Log file to receive output.</param>
      /// <param name="bAppend">Flag: append to existing file (if any).</param>
      /// <param name="logLevels">Mask indicating log levels of interest.</param>
      public EZLogger
        (string logFilename,
         bool bAppend,
         uint logLevels)
      {
        _logFilename = logFilename;
        _bAppend = bAppend;
        _levels = logLevels;
      }

      /// <summary>
      /// Constructs a EZLogger.
      /// </summary>
      /// <param name="logPath">Log file save path.</param>
      /// <param name="bAppend">Flag: append to existing file (if any).</param>
      /// <param name="logLevels">Mask indicating log levels of interest.</param>
      public EZLogger(string logPath)
      {
          //create folder
          _logPath = logPath;

          DirectoryInfo dir = new DirectoryInfo(_logPath);
          //folder check
          if (dir.Exists == false)
          {
              dir.Create(); //새로 생성
          }

          _logFilename = MakeValidLogFileName();
          _bAppend = true;
          _levels = (uint) Level.All;
      }

      /// <summary>
      /// Private default constructor.
      /// </summary>
      private EZLogger()
      {
      }



    #endregion

    #region Properties

      /// <summary>
      /// Gets and sets the log level.
      /// </summary>
      public uint Levels {
        get {
          return _levels;
        }
        set {
          _levels = value;
        }
      }

      /// <summary>
      /// Retrieves the logger's state.
      /// </summary>
      public State LoggerState {
        get {
          return _state;
        }
      }

    #endregion

    #region Operations

      /// <summary>
      /// Starts logging.
      /// </summary>
      /// <returns>true if successful, false otherwise.</returns>
      public bool Start()
      {
        lock (this) {
          // Fail if logging has already been started
          if (LoggerState != State.Stopped)
              return false;

          // Fail if the log file isn't specified
          if (String.IsNullOrEmpty (_logFilename))
              return false;

          // Delete log file if it exists
          if (!_bAppend) {
              try {
                File.Delete (_logFilename);
              }
              catch (Exception) {
                return false;
              }
          }

          // Open file for writing - return on error
          if (!File.Exists (_logFilename)) {
              try {
                _logFile = File.CreateText (_logFilename);
              }
              catch (Exception) {
                _logFile = null;
                return false;
              }
          } else {
              try {
                _logFile = File.AppendText (_logFilename);
              }
              catch (Exception) {
                _logFile = null;
                return false;
              }
          }
          _logFile.AutoFlush = true;

          // Return successfully
          _state = EZLogger.State.Running;
          return true;
        }
      }

      /// <summary>
      /// Temporarily suspends logging.
      /// </summary>
      /// <returns>true if successful, false otherwise.</returns>
      public bool Pause()
      {
        lock (this) {
          // Fail if logging hasn't been started
          if (LoggerState != State.Running)
              return false;

          // Pause the logger
          _state = EZLogger.State.Paused;
          return true;
        }
      }

      /// <summary>
      /// Resumes logging.
      /// </summary>
      /// <returns>true if successful, false otherwise.</returns>
      public bool Resume()
      {
        lock (this) {
          // Fail if logging hasn't been paused
          if (LoggerState != State.Paused)
              return false;

          // Resume logging
          _state = EZLogger.State.Running;
          return true;
        }
      }

      /// <summary>
      /// Stops logging.
      /// </summary>
      /// <returns>true if successful, false otherwise.</returns>
      public bool Stop()
      {
        lock (this) {
          // Fail if logging hasn't been started
          if (LoggerState != State.Running)
              return false;

          // Stop logging
          try {
            _logFile.Close();
            _logFile = null;
          }
          catch (Exception) {
            return false;
          }
          _state = EZLogger.State.Stopped;
          return true;
        }
      }

      /// <summary>
      /// Logs a debug message.
      /// </summary>
      /// <param name="msg">The message.</param>
      /// <returns>true if successful, false otherwise.</returns>
      public bool Debug
        (string msg)
      {
        _debugMsgs++;
        return WriteLogMsg (Level.Debug, msg);
      }

      /// <summary>
      /// Logs an informational message.
      /// </summary>
      /// <param name="msg">The message.</param>
      /// <returns>true if successful, false otherwise.</returns>
      public bool Info
        (string msg)
      {
        _infoMsgs++;
        return WriteLogMsg (Level.Info, msg);
      }

      /// <summary>
      /// Logs a success message.
      /// </summary>
      /// <param name="msg">The message.</param>
      /// <returns>true if successful, false otherwise.</returns>
      public bool Success
        (string msg)
      {
        _successMsgs++;
        return WriteLogMsg (Level.Success, msg);
      }

      /// <summary>
      /// Logs a warning message.
      /// </summary>
      /// <param name="msg">The message.</param>
      /// <returns>true if successful, false otherwise.</returns>
      public bool Warning
        (string msg)
      {
        _warningMsgs++;
        return WriteLogMsg (Level.Warning, msg);
      }

      /// <summary>
      /// Logs an error message.
      /// </summary>
      /// <param name="msg">The message.</param>
      /// <returns>true if successful, false otherwise.</returns>
      public bool Error
        (string msg)
      {
        _errorMsgs++;
        return WriteLogMsg (Level.Error, msg);
      }

      /// <summary>
      /// Logs a fatal error message.
      /// </summary>
      /// <param name="msg">The message.</param>
      /// <returns>true if successful, false otherwise.</returns>
      public bool Fatal
        (string msg)
      {
        _fatalMsgs++;
        return WriteLogMsg (Level.Fatal, msg);
      }

      /// <summary>
      /// Retrieves the count of messages logged at one or more levels.
      /// </summary>
      /// <param name="levelMask">Mask indicating levels of interest.</param>
      /// <returns></returns>
      public uint GetMessageCount
        (uint levelMask)
      {
        uint uMessages = 0;
        if ((levelMask & ((uint) Level.Debug)) != 0)
            uMessages += _debugMsgs;
        if ((levelMask & ((uint) Level.Info)) != 0)
            uMessages += _infoMsgs;
        if ((levelMask & ((uint) Level.Success)) != 0)
            uMessages += _successMsgs;
        if ((levelMask & ((uint) Level.Warning)) != 0)
            uMessages += _warningMsgs;
        if ((levelMask & ((uint) Level.Error)) != 0)
            uMessages += _errorMsgs;
        if ((levelMask & ((uint) Level.Fatal)) != 0)
            uMessages += _fatalMsgs;
        return uMessages;
      }


    #endregion

    #region Helper methods

      /// <summary>
      ///generate unique log file name
      /// </summary>
      private string MakeValidLogFileName()
      {
          bool bTodayLogExist = false;
          string strLogFileName;
          int nCnt = 0;

          //make file name by today date
          DateTime dtNow = new DateTime();
          dtNow = DateTime.Now;
          string strTodayLogDate = String.Format("{0:yyyy-MM-dd}", DateTime.Now);
          string strFileName = "";

          string[] strFiles = Directory.GetFiles(_logPath, "*.log");
          foreach (string strFile in strFiles)
          {
              //exist file check, yyyy-MM-dd.log or yyyy-MM-dd_n.log
              strFileName = Path.GetFileName(strFile);
              if (strFileName.Substring(0, 10) == strTodayLogDate)
              {
                  bTodayLogExist = true;
                  nCnt++;
              }
          }

          if (bTodayLogExist == false)
              strLogFileName = String.Format("{0:yyyy-MM-dd}.log", DateTime.Now);
          else
              strLogFileName = String.Format("{0:yyyy-MM-dd}_{1}.log", DateTime.Now, nCnt);

          return _logPath + "\\" + strLogFileName;

      }

      /// <summary>
      /// Writes a log message.
      /// </summary>
      /// <param name="level"></param>
      /// <param name="msg"></param>
      /// <returns></returns>
      private bool WriteLogMsg
        (Level level,
         string msg)
      {
        lock (this) {

          // Fail if logger hasn't been started
          if (LoggerState == State.Stopped)
              return false;

          // Ignore message logging is paused or it doesn't pass the filter
          if ((LoggerState == State.Paused) || ((_levels & (uint) level) != (uint) level))
              return true;

          // Write log message
          DateTime tmNow = DateTime.Now;
          string logMsg = String.Format ("{0} {1}  {2}: {3}",
                                         tmNow.ToShortDateString(), tmNow.ToLongTimeString(),
                                         level.ToString().Substring (0, 1), msg);

          //filedate check. if changed make new logfile
          string strTodayLogDate = String.Format("{0:yyyy-MM-dd}", DateTime.Now);

          string strFileName = Path.GetFileName(_logFilename);
          if (strFileName.Substring(0, 10) != strTodayLogDate)
          {
              Stop();

              _debugMsgs = 0;
              _errorMsgs = 0;
              _fatalMsgs = 0;
              _infoMsgs = 0;
              _successMsgs = 0;
              _warningMsgs = 0;

              _logFilename = MakeValidLogFileName();
              Start();
          }

          _logFile.WriteLine (logMsg);
          return true;
        }
      }

    #endregion

    #region Fields

      /// <summary>Name of the log file.</summary>
      private string _logFilename;

      /// <summary>Name of the log path.</summary>
      private string _logPath;

      /// <summary>Flag: append to existing file (if any).</summary>
      private bool _bAppend = true;

      /// <summary>The log file.</summary>
      private StreamWriter _logFile = null;

      /// <summary>Levels to be logged.</summary>
      private uint _levels = (uint) (Level.Warning | Level.Error | Level.Fatal);

      /// <summary>The logger's state.</summary>
      private State _state = State.Stopped;

      /// <summary>Number of debug messages that have been logged.</summary>
      private uint _debugMsgs = 0;

      /// <summary>Number of informational messages that have been logged.</summary>
      private uint _infoMsgs = 0;

      /// <summary>Number of success messages that have been logged.</summary>
      private uint _successMsgs = 0;

      /// <summary>Number of warning messages that have been logged.</summary>
      private uint _warningMsgs = 0;

      /// <summary>Number of error messages that have been logged.</summary>
      private uint _errorMsgs = 0;

      /// <summary>Number of fatal messages that have been logged.</summary>
      private uint _fatalMsgs = 0;

    #endregion
  }
}

QuestionGreat and Simple! MDI Form Question... Pin
Travis Hamera29-Dec-11 19:47
professionalTravis Hamera29-Dec-11 19:47 
AnswerRe: Great and Simple! MDI Form Question... Pin
Ravi Bhavnani30-Dec-11 2:35
professionalRavi Bhavnani30-Dec-11 2:35 
GeneralRe: Great and Simple! MDI Form Question... Pin
Travis Hamera31-Dec-11 7:33
professionalTravis Hamera31-Dec-11 7:33 
GeneralRe: Great and Simple! MDI Form Question... Pin
Ravi Bhavnani31-Dec-11 7:46
professionalRavi Bhavnani31-Dec-11 7:46 
GeneralMy vote of 5 Pin
polczym23-Oct-11 3:11
polczym23-Oct-11 3:11 
GeneralRe: My vote of 5 Pin
Ravi Bhavnani26-Dec-11 9:31
professionalRavi Bhavnani26-Dec-11 9:31 
GeneralWindow CE Pin
Member 23087665-Aug-10 5:01
Member 23087665-Aug-10 5:01 
GeneralRe: Window CE Pin
Ravi Bhavnani5-Aug-10 6:46
professionalRavi Bhavnani5-Aug-10 6:46 
GeneralRe: Window CE Pin
Member 23087665-Aug-10 14:57
Member 23087665-Aug-10 14:57 
AnswerRe: Window CE Pin
Ravi Bhavnani5-Aug-10 15:28
professionalRavi Bhavnani5-Aug-10 15:28 
GeneralRe: Window CE Pin
Member 23087665-Aug-10 18:38
Member 23087665-Aug-10 18:38 
GeneralSimple and good Pin
Wolfgang Mena-Bruhn29-May-07 7:38
Wolfgang Mena-Bruhn29-May-07 7:38 
GeneralRe: Simple and good Pin
Ravi Bhavnani29-May-07 7:44
professionalRavi Bhavnani29-May-07 7:44 
QuestionRolling File Appender ? Pin
mejax2-Apr-07 8:21
mejax2-Apr-07 8:21 
AnswerRe: Rolling File Appender ? Pin
Ravi Bhavnani2-Apr-07 8:24
professionalRavi Bhavnani2-Apr-07 8:24 
QuestionRe: Rolling File Appender ? Pin
mejax3-Apr-07 6:55
mejax3-Apr-07 6:55 
AnswerRe: Rolling File Appender ? Pin
Ravi Bhavnani3-Apr-07 7:25
professionalRavi Bhavnani3-Apr-07 7:25 
QuestionLevel Assignment question Pin
davidnr27-Mar-07 2:33
davidnr27-Mar-07 2:33 
AnswerRe: Level Assignment question Pin
Ravi Bhavnani27-Mar-07 2:40
professionalRavi Bhavnani27-Mar-07 2:40 
GeneralRe: Level Assignment question Pin
davidnr27-Mar-07 3:08
davidnr27-Mar-07 3:08 
AnswerRe: Level Assignment question Pin
Ravi Bhavnani27-Mar-07 5:09
professionalRavi Bhavnani27-Mar-07 5:09 
GeneralRe: Level Assignment question Pin
davidnr27-Mar-07 7:44
davidnr27-Mar-07 7:44 
GeneralSuperb Pin
leonvd11-Dec-06 23:03
leonvd11-Dec-06 23:03 
GeneralRe: Superb Pin
Ravi Bhavnani12-Dec-06 2:05
professionalRavi Bhavnani12-Dec-06 2:05 

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.