Monday, November 10, 2014

Logging Utility

Background

Sometimes you need to introduce excessive logging in a workflow, web application, complex runtime application logging is a very important thing in order to identify issues. I have a logger by looking into various loggers available over the web. The sole purpose of this logger for me was to be applied to a workflow which was written in C# using Windows Workflow Foundation.

Logging seems quite easy if it's an application which runs one time or is a sequential one where the rate of concurrent users is less or negligible you just write all the logging lines down in a file and that's all. But imagine when there are multiple people working on an application for which you are logging you will definitely come across the issue of writing to the file when it's already in use.

Implementation Details 


Here are details of the workflow with a test application; I have tried up to 20 threads which are trying to access the file for writing and its working fine.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WriteToFileMultiThreaded;
namespace WriteToFileMultiThreaded
{
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
/// <summary>
/// A Logging class implementing the Singleton pattern and an internal Queue to be flushed perdiodically
/// </summary>
public class LogWriter
{
private static LogWriter instance;
private static Queue<Log> logQueue;
private static string logDir = "";
private static string logFile = "WorkflowLog.txt";
private static int maxLogAge = int.Parse("5184000");
private static int queueSize = int.Parse("20");
private static DateTime LastFlushed = DateTime.Now;
/// <summary>
/// Private constructor to prevent instance creation
/// </summary>
private LogWriter() { }
/// <summary>
/// An LogWriter instance that exposes a single instance
/// </summary>
public static LogWriter Instance
{
get
{
// If the instance is null then create one and init the Queue
if (instance == null)
{
instance = new LogWriter();
logQueue = new Queue<Log>();
}
return instance;
}
}
/// <summary>
/// The single instance method that writes to the log file
/// </summary>
/// <param name="message">The message to write to the log</param>
public void WriteToLog(string message)
{
// Lock the queue while writing to prevent contention for the log file
lock (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 Queue
if (logQueue.Count >= queueSize || DoPeriodicFlush())
{
FlushLog();
}
}
}
private bool DoPeriodicFlush()
{
TimeSpan logAge = DateTime.Now - LastFlushed;
if (logAge.TotalSeconds >= maxLogAge)
{
LastFlushed = DateTime.Now;
return true;
}
else
{
return false;
}
}
/// <summary>
/// Flushes the Queue to the physical log file
/// </summary>
private void 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 write
using (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));
}
}
}
}
}
/// <summary>
/// A Log class to store the message and the Date and Time the log entry was created
/// </summary>
public class Log
{
public string Message { get; set; }
public string LogTime { get; set; }
public string 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");
}
}
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 20; i++)
{
string threadName = "Thread " + Convert.ToString(i + 1);
Thread t = new Thread(() => ThreadData(threadName));
t.Start();
}
}
static void ThreadData(string threadName)
{
LogWriter writer = LogWriter.Instance;
for (int i = 0; i < 1000; i++)
{
writer.WriteToLog(threadName + " Writing" + Environment.NewLine);
//System.IO.File.AppendAllText("test.txt", "Thread One Writing Line " + i + Environment.NewLine);
}
}
static void ThreadTwo()
{
LogWriter writer = LogWriter.Instance;
for (int i = 0; i < 1000; i++)
{
writer.WriteToLog("Thread Two Writing Line " + i + Environment.NewLine);
//System.IO.File.AppendAllText("test.txt", "Thread Two Writing Line " + i);
}
}
}
}
view raw logger.cs hosted with ❤ by GitHub