I am trying to design a class that will log errors to a text file, the application is using threads heavily so it may be that 2 errors that need to be written at the same time. I know there are 3rd party solutions available but for this particular case I would like to roll my own. What I have at the moment seems to work, I was just curious to see if anyone has any tips,improvements etc. The main aim is to not Block the UI while waiting for errors to be logged.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
using System.Collections.Concurrent;
using System.Windows.Forms;
using System.Threading.Tasks;
/// <summary>
/// Error Logger will keep a list of all the exceptions and write them to a log periodically
/// This is to ensure we dont get file locks trying to write to the file from multiple threads
/// </summary>
public class ErrorLogger : IDisposable
{
internal class ExceptionInfo
{
public Exception ExceptionObject { get; set; }
public DateTime Date { get; set; }
public int UserId { get; set; }
}
private static ErrorLogger _instance = null;
public static ErrorLogger Instance
{
get
{
if (_instance == null)
{
_instance = new ErrorLogger();
}
return _instance;
}
}
private Timer _timer = new Timer();
private Task _writeTask = null;
private ConcurrentQueue<ExceptionInfo> _exceptions = new ConcurrentQueue<ExceptionInfo>();
public ErrorLogger()
{
_timer.Interval = 1000;
_timer.Tick += new EventHandler(_timer_Tick);
_timer.Start();
}
void _timer_Tick(object sender, EventArgs e)
{
if (_writeTask == null)
{
_writeTask = Task.Factory.StartNew(delegate
{
WriteLog();
});
}
else if (_writeTask.IsCompleted && _exceptions.Count > 0)
{
_writeTask.Dispose();
_writeTask = Task.Factory.StartNew(delegate
{
WriteLog();
});
}
}
public void LogErrror(Exception ex)
{
_exceptions.Enqueue(new ExceptionInfo() { ExceptionObject = ex, Date = DateTime.Now });
}
private void WriteLog()
{
string directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"First Choice Software\Pronet Outlook");
string filePath = Path.Combine(directory, @"ErrorLog.txt");
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
if (!File.Exists(filePath))
{
File.Create(filePath).Close();
}
ExceptionInfo exInfo = null;
System.IO.StreamWriter sw = null;
try
{
sw = new System.IO.StreamWriter(filePath, true);
while (_exceptions.TryDequeue(out exInfo))
{
string errorMessage = string.Format("{1}{5}{5}{5}{5}{5}{1}Version : {6}{1}Date : {0}{1}UserId : {2}{1}Message : {3}{1}Stack Trace : {4}", DateTime.Now,
Environment.NewLine,
exInfo.UserId,
exInfo.ExceptionObject.Message,
exInfo.ExceptionObject.StackTrace,
"========",
Assembly.GetExecutingAssembly().GetName().Version.ToString(4));
Debug.Write(errorMessage);
char[] c = errorMessage.ToCharArray();
sw.Write(c, 0, c.Length);
}
}
catch
{
}
finally
{
if (sw != null)
{
sw.Close();
sw.Dispose();
}
}
}
public void LogErrror(Exception ex, int pUserId)
{
_exceptions.Enqueue(new ExceptionInfo() { ExceptionObject = ex, Date = DateTime.Now,UserId = pUserId });
}
public void Dispose()
{
_exceptions.Enqueue(new ExceptionInfo() { ExceptionObject = new Exception("Disposing") });
_timer.Stop();
_timer.Dispose();
if (_writeTask != null)
{
//If there are exceptions still to be logged, log them now, blocking UI now is ok
if (_writeTask.IsCompleted && _exceptions.Count > 0)
{
WriteLog();
}
else if (!_writeTask.IsCompleted)
{
_writeTask.Wait();
}
_writeTask.Dispose();
}
_exceptions = null;
_instance.Dispose();
_instance = null;
}
}
Dispose()
has a race condition (what happens if_writeTask
is checked when it hasn't completed yet, but already exited the loop?). Also, I think it will cause stack overflow, since it's calling itself. \$\endgroup\$