diff --git a/MapleLib/Helpers/ErrorLogger.cs b/MapleLib/Helpers/ErrorLogger.cs index b86ac06..2fd686c 100644 --- a/MapleLib/Helpers/ErrorLogger.cs +++ b/MapleLib/Helpers/ErrorLogger.cs @@ -24,12 +24,16 @@ namespace MapleLib.Helpers { public static class ErrorLogger { - private static readonly List errorList = new List(); + private static readonly object _lock = new object(); + private static readonly List _errorList = new List(); public static void Log(ErrorLevel level, string message) { - lock (errorList) - errorList.Add(new Error(level, message)); + if (string.IsNullOrWhiteSpace(message)) + throw new ArgumentNullException(nameof(message), "Error message cannot be null or empty."); + + lock (_lock) + _errorList.Add(new Error(level, message, DateTime.UtcNow)); } /// @@ -38,7 +42,7 @@ public static void Log(ErrorLevel level, string message) /// public static int NumberOfErrorsPresent() { - return errorList.Count; + return _errorList.Count; } /// @@ -47,7 +51,7 @@ public static int NumberOfErrorsPresent() /// public static bool ErrorsPresent() { - return errorList.Count > 0; + return _errorList.Any(); } /// @@ -55,56 +59,94 @@ public static bool ErrorsPresent() /// public static void ClearErrors() { - lock (errorList) - errorList.Clear(); + lock (_lock) + _errorList.Clear(); } /// - /// Logs all pending errors in the queue to file, and clears the queue + /// Logs all pending errors in the queue to file, grouped by error level, and clears the queue /// - /// + /// The path to the log file + /// Thrown when filename is null or empty + /// Thrown when there's an error writing to the file public static void SaveToFile(string filename) { + if (string.IsNullOrWhiteSpace(filename)) + throw new ArgumentNullException(nameof(filename), "Filename cannot be null or empty."); + if (!ErrorsPresent()) return; - using (StreamWriter sw = new StreamWriter(File.Open(filename, FileMode.Append, FileAccess.Write, FileShare.Read))) + List errorsCopy; + lock (_lock) { - sw.Write("----- Start of the error log. ["); - sw.Write(DateTime.Today.ToString()); - sw.Write("] -----"); - sw.WriteLine(); + errorsCopy = new List(_errorList); + ClearErrors(); + } - List errorList_; - lock (errorList) + var groupedErrors = errorsCopy + .GroupBy(e => e.Level) + .OrderBy(g => g.Key); + + var sb = new StringBuilder(); + sb.AppendLine($"----- Start of the error log. [{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss.fff}] -----"); + + foreach (var errorGroup in groupedErrors) + { + sb.AppendLine(); + sb.AppendLine($"=== {errorGroup.Key} Errors ==="); + + foreach (var error in errorGroup.OrderBy(e => e.Timestamp)) { - errorList_ = new List(errorList); // make a copy before writing - ClearErrors(); + sb.AppendLine($"[{error.Timestamp:HH:mm:ss.fff}] : {error.Message}"); } + } - foreach (Error e in errorList_) - { - sw.Write("["); - sw.Write(e.level.ToString()); - sw.Write("] : "); - sw.Write(e.message); + sb.AppendLine(); + sb.AppendLine($"----- End of the error log. [{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss.fff}] -----"); + sb.AppendLine(); - sw.WriteLine(); - } - sw.WriteLine(); + // Use FileShare.ReadWrite to allow other processes to read the file while we're writing + using (var sw = new StreamWriter(File.Open(filename, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))) + { + sw.Write(sb.ToString()); + } + } + + /// + /// Gets a snapshot of current errors grouped by error level + /// + /// Dictionary with error levels and their corresponding error messages + public static Dictionary> GetErrorSnapshot() + { + lock (_lock) + { + return _errorList + .GroupBy(e => e.Level) + .ToDictionary( + g => g.Key, + g => g.ToList() + ); } } } public class Error { - internal ErrorLevel level; - internal string message; + public ErrorLevel Level { get; } + public string Message { get; } + public DateTime Timestamp { get; } + + internal Error(ErrorLevel level, string message, DateTime timestamp) + { + Level = level; + Message = message; + Timestamp = timestamp; + } - internal Error(ErrorLevel level, string message) + public override string ToString() { - this.level = level; - this.message = message; + return $"[{Level}] [{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] : {Message}"; } }