I was doing some work for a client recently that had all sorts of issues with logging due to some old code from the original developers. It had multiple threads all trying to write to the log file simultaneously. Needless to say loads of logging just fell by the way side as the file could not be locked for writing.

The Singleton class below implements an internal queue that stacks up Log Messages and when 1 of 2 thresholds is reached (number of items in queue or queue age) the messages all get flushed to disk.

Calling the class is done with 2 lines of code:

LogWriter writer = LogWriter.Instance; writer.WriteToLog(message);

Full source code below the break



using System; using System.Collections.Generic; using System.Configuration; using System.IO; namespace BondiGeek.Logging { ///

/// A Logging class implementing the Singleton pattern and an internal Queue to be flushed perdiodically/// publicclass LogWriter { privatestatic LogWriter instance; privatestatic Queue logQueue; privatestaticstring logDir = ; privatestaticstring logFile = ; privatestaticint maxLogAge = int.Parse(in seconds or Config Setting>); privatestaticint queueSize = int.Parse(privatestatic DateTime LastFlushed = DateTime.Now; /// /// Private constructor to prevent instance creation/// private LogWriter() { } /// /// An LogWriter instance that exposes a single instance/// publicstatic LogWriter Instance { get { // If the instance is null then create one and init the Queueif (instance == null) { instance = new LogWriter(); logQueue = new Queue(); } return instance; } } /// /// The single instance method that writes to the log file/// /// The message to write to the logpublicvoid WriteToLog(string message) { // Lock the queue while writing to prevent contention for the log filelock (logQueue) { // Create the entry and push to the Queue Log logEntry = new Log(message); logQueue.Enqueue(logEntry); // If we have reached the Queue Size then flush the Queueif (logQueue.Count >= queueSize || DoPeriodicFlush()) { FlushLog(); } } } privatebool DoPeriodicFlush() { TimeSpan logAge = DateTime.Now - LastFlushed; if (logAge.TotalSeconds >= maxLogAge) { LastFlushed = DateTime.Now; returntrue; } else { returnfalse; } } /// /// Flushes the Queue to the physical log file/// privatevoid FlushLog() { while (logQueue.Count > 0) { Log entry = logQueue.Dequeue(); string logPath = logDir + entry.LogDate + "_" + logFile; // This could be optimised to prevent opening and closing the file for each writeusing (FileStream fs = File.Open(logPath, FileMode.Append, FileAccess.Write)) { using (StreamWriter log = new StreamWriter(fs)) { log.WriteLine(string.Format("{0}\t{1}",entry.LogTime,entry.Message)); } } } } } /// /// A Log class to store the message and the Date and Time the log entry was created/// publicclass Log { publicstring Message { get; set; } publicstring LogTime { get; set; } publicstring LogDate { get; set; } public Log(string message) { Message = message; LogDate = DateTime.Now.ToString("yyyy-MM-dd"); LogTime = DateTime.Now.ToString("hh:mm:ss.fff tt"); } } }

Enjoy!

BondiGeek