I have a simple class that I use to logging errors and other informations from my applications.
I wondering what will happen when 2 or more threads of the same application would like to log something to file. What will happen when they call Log() function at the same time?
The more important question is how could I make it thread safe and safely use it in multithreaded applications?
public static class Logger // UWAGA!!! - now not thread safe - tylko do aplikacji jednowątkowych
{
// lockers
private static readonly object _consoleLocker = new object();
private static readonly object _diskDriveLocker = new object();
private static LoggerLevel _logLevel = LoggerLevel.Trace;
private static List<LoggerTarget> _logTargets = new List<LoggerTarget>() { GostcompUtils.LoggerTarget.File };
private static string _className = "";
private static string _fileName = "";
public static LoggerLevel Level
{
get
{
return _logLevel;
}
set
{
_logLevel = value;
}
}
public static List<LoggerTarget> Targets
{
get
{
return _logTargets;
}
set
{
_logTargets = value;
}
}
public static LoggerTarget AddTarget
{
set
{
if (_logTargets != null)
_logTargets.Add(value);
}
}
public static LoggerTarget RemoveTarget
{
set
{
if (_logTargets != null && _logTargets.Contains(value))
_logTargets.Remove(value);
}
}
public static string ClassName
{
get
{
return _className;
}
set
{
_className = value;
}
}
public static string FileName
{
get
{
return _fileName;
}
set
{
_fileName = value;
}
}
public static void Log(string message, LoggerLevel level, Exception exception = null, string extraMessage = null) // najprostsza funkcjonalna wersja f. Log(), w tej wersji FileName, Level i Targets muszą być ustawione wcześniej
{
if ((int)level <= (int)Level)
{
if (_logTargets != null && _logTargets.Count() > 0)
{
if (string.IsNullOrEmpty(_fileName))
ProcessLogMessage(message, exception, _logTargets, level, null, extraMessage, "defaultLogFile.txt");
else
ProcessLogMessage(message, exception, _logTargets, level, null, extraMessage, _fileName);
}
else
throw new Exception("No targets set to log the message. Please do it with Targets or AddTarget propery or use another Log() method.");
}
}
//
// inne wersje metody Log()
//
// 1. zeby zalogowac do innego pliku pojedynczy log
// 2. zeby zalogowac do konkretnego targeta innego niz zdefiniowane w klasie
//
// prostsze wersje funkcji do logowania
//
public static void Error(string message, Exception exception = null)
{
Log(message, LoggerLevel.Error, exception);
}
public static void Warn(string message, Exception exception = null)
{
Log(message, LoggerLevel.Warn, exception);
}
public static void Info(string message)
{
Log(message, LoggerLevel.Info);
}
public static void Debug(string message)
{
Log(message, LoggerLevel.Debug);
}
public static void Trace(string message)
{
Log(message, LoggerLevel.Trace);
}
static private void ProcessLogMessage(string message, Exception exception, List<LoggerTarget> logTargets, LoggerLevel logLevel, string className, string extraMessage, string fileName)
{
StringBuilder logMessage = PrepareTextOfLogMessage(message, exception, logLevel, className, extraMessage);
string applicationDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
applicationDirectory = applicationDirectory.Replace("file:\\", "");
string loggerFile = Path.Combine(applicationDirectory, fileName);
if (logTargets.Contains(LoggerTarget.File))
{
lock (_diskDriveLocker)
{
WriteLogToFile(logMessage.ToString(), loggerFile);
}
}
if (logTargets.Contains(LoggerTarget.Console))
{
lock (_consoleLocker)
{
Console.WriteLine(logMessage.ToString());
}
}
}
static private StringBuilder PrepareTextOfLogMessage(string message, Exception exception, LoggerLevel logLevel, string className, string extraMessage)
{
StringBuilder logMessage = new StringBuilder("");
if (string.IsNullOrEmpty(className))
logMessage.Append(String.Format("[{0}] [{1}] {2} {3}", DateTime.Now.ToString(), logLevel.ToString(), message, extraMessage) + Environment.NewLine);
else
logMessage.Append(String.Format("[{0}] [{1}] <{2}> {3} {4}", DateTime.Now.ToString(), logLevel.ToString(), className, message, extraMessage) + Environment.NewLine);
if (exception != null)
{
logMessage.Append("***********************************************************************************************************" + Environment.NewLine);
logMessage.Append("* EXCEPTION OCCURANCE: " + Environment.NewLine);
logMessage.Append("* ---------------------------------------------------------------------------------------------------------" + Environment.NewLine);
logMessage.Append("* Exception Message: " + Environment.NewLine + exception.Message + Environment.NewLine);
logMessage.Append("* ---------------------------------------------------------------------------------------------------------" + Environment.NewLine);
logMessage.Append("* Exception Source: " + Environment.NewLine + exception.Source + Environment.NewLine);
logMessage.Append("* ---------------------------------------------------------------------------------------------------------" + Environment.NewLine);
logMessage.Append("* Exception StackTrace: " + Environment.NewLine + exception.StackTrace + Environment.NewLine);
logMessage.Append("* ---------------------------------------------------------------------------------------------------------" + Environment.NewLine);
logMessage.Append("* Exception's InnerException:" + Environment.NewLine + exception.StackTrace + Environment.NewLine);
logMessage.Append("***********************************************************************************************************" + Environment.NewLine);
// pozostałe informacje z exceptiona
}
return logMessage;
}
private static void WriteLogToFile(string strLogMessage, string strLogFile)
{
using (StreamWriter swLog = new StreamWriter(strLogFile, true))
{
swLog.Write(strLogMessage);
}
}
}
public enum LoggerLevel
{
Error = 0,
Warn,
Info,
Debug,
Trace,
}
public enum LoggerTarget
{
File = 0,
Console,
MessageBox,
Email,
Database,
}