diff --git a/RombaSharp/Program.cs b/RombaSharp/Program.cs index de791ff8..39de014f 100644 --- a/RombaSharp/Program.cs +++ b/RombaSharp/Program.cs @@ -26,7 +26,12 @@ namespace RombaSharp public static void Main(string[] args) { // Perform initial setup and verification - Globals.Logger = new Logger("romba.log"); + Globals.Logger = new Logger("romba.log") + { + AppendPrefix = true, + LowestLogLevel = LogLevel.VERBOSE, + ThrowOnError = false, + }; // Create a new Help object for this program _help = RetrieveHelp(); diff --git a/SabreTools.Library/DatFiles/ItemDictionary.cs b/SabreTools.Library/DatFiles/ItemDictionary.cs index 3710b01e..da1e1595 100644 --- a/SabreTools.Library/DatFiles/ItemDictionary.cs +++ b/SabreTools.Library/DatFiles/ItemDictionary.cs @@ -1,4 +1,5 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -1410,13 +1411,13 @@ namespace SabreTools.Library.DatFiles dirStats.ResetStatistics(); } - Globals.Logger.Verbose($"Beginning stat collection for '{file.CurrentPath}'", false); + Globals.Logger.Verbose($"Beginning stat collection for '{file.CurrentPath}'"); List games = new List(); DatFile datdata = DatFile.CreateAndParse(file.CurrentPath); datdata.Items.BucketBy(Field.Machine_Name, DedupeType.None, norename: true); // Output single DAT stats (if asked) - Globals.Logger.User($"Adding stats for file '{file.CurrentPath}'\n", false); + Globals.Logger.User($"Adding stats for file '{file.CurrentPath}'\n"); if (single) { reports.ForEach(report => report.ReplaceStatistics(datdata.Header.FileName, datdata.Items.Keys.Count, datdata.Items)); @@ -1460,8 +1461,7 @@ namespace SabreTools.Library.DatFiles // Output footer if needed reports.ForEach(report => report.WriteFooter()); - Globals.Logger.User(@" -Please check the log folder if the stats scrolled offscreen", false); + Globals.Logger.User($"{Environment.NewLine}Please check the log folder if the stats scrolled offscreen"); } /// diff --git a/SabreTools.Library/Data/Constants.cs b/SabreTools.Library/Data/Constants.cs index fd1dd79c..44bb18be 100644 --- a/SabreTools.Library/Data/Constants.cs +++ b/SabreTools.Library/Data/Constants.cs @@ -12,8 +12,8 @@ namespace SabreTools.Library.Data /// /// The current toolset version to be used by all child applications /// - public readonly static string Version = $"v1.0.4"; - //public readonly static string Version = $"v1.0.4-{File.GetCreationTime(Assembly.GetExecutingAssembly().Location):yyyy-MM-dd HH:mm:ss}"; + //public readonly static string Version = $"v1.0.4"; + public readonly static string Version = $"v1.0.4-{File.GetCreationTime(Assembly.GetExecutingAssembly().Location):yyyy-MM-dd HH:mm:ss}"; public const int HeaderHeight = 3; #region 0-byte file constants diff --git a/SabreTools.Library/Logging/LogEventArgs.cs b/SabreTools.Library/Logging/LogEventArgs.cs new file mode 100644 index 00000000..0463915c --- /dev/null +++ b/SabreTools.Library/Logging/LogEventArgs.cs @@ -0,0 +1,61 @@ +using System; + +namespace SabreTools.Library.Logging +{ + /// + /// Generic delegate type for log events + /// + public delegate void LogEventHandler(object sender, LogEventArgs args); + + /// + /// Logging specific event arguments + /// + public class LogEventArgs : EventArgs + { + /// + /// LogLevel for the event + /// + public LogLevel LogLevel { get; set; } = LogLevel.VERBOSE; + + /// + /// Log statement to be printed + /// + public string Statement { get; set; } = null; + + /// + /// Exception to be passed along to the event handler + /// + public Exception Exception { get; set; } = null; + + /// + /// Total count for progress log events + /// + public long? TotalCount { get; set; } = null; + + /// + /// Current count for progress log events + /// + public long? CurrentCount { get; set; } = null; + + /// + /// Statement and exception constructor + /// + public LogEventArgs(LogLevel logLevel = LogLevel.VERBOSE, string statement = null, Exception exception = null) + { + this.LogLevel = logLevel; + this.Statement = statement; + this.Exception = exception; + } + + /// + /// Progress constructor + /// + public LogEventArgs(long total, long current, LogLevel logLevel = LogLevel.VERBOSE, string statement = null) + { + this.LogLevel = logLevel; + this.Statement = statement; + this.TotalCount = total; + this.CurrentCount = current; + } + } +} diff --git a/SabreTools.Library/Logging/Logger.cs b/SabreTools.Library/Logging/Logger.cs index 376ebf4c..387235c3 100644 --- a/SabreTools.Library/Logging/Logger.cs +++ b/SabreTools.Library/Logging/Logger.cs @@ -35,6 +35,11 @@ namespace SabreTools.Library.Logging /// public LogLevel LowestLogLevel { get; set; } = LogLevel.VERBOSE; + /// + /// Determines whether to prefix log lines with level and datetime + /// + public bool AppendPrefix { get; set; } = true; + /// /// Determines whether to throw if an exception is logged /// @@ -71,6 +76,8 @@ namespace SabreTools.Library.Logging #endregion + #region Constructors + /// /// Initialize a console-only logger object /// @@ -86,6 +93,7 @@ namespace SabreTools.Library.Logging /// True to add a date string to the filename (default), false otherwise public Logger(string filename, bool addDate = true) { + // Set and create the output if (addDate) Filename = $"{Path.GetFileNameWithoutExtension(filename)} ({DateTime.Now:yyyy-MM-dd HH-mm-ss}).{PathExtensions.GetNormalizedExtension(filename)}"; else @@ -97,16 +105,25 @@ namespace SabreTools.Library.Logging Start(); } + #endregion + + #region Control + /// /// Start logging by opening output file (if necessary) /// /// True if the logging was started correctly, false otherwise public bool Start() { + // Setup the logging handler to always use the internal log + LogEventHandler += HandleLogEvent; + + // Start the logging StartTime = DateTime.Now; if (!LogToFile) return true; + // Setup file output and perform initial log try { FileStream logfile = FileExtensions.TryCreate(Path.Combine(LogDirectory, Filename)); @@ -183,14 +200,61 @@ namespace SabreTools.Library.Logging return true; } + #endregion + + #region Event Handling + + /// + /// Handler for log events + /// + public static event LogEventHandler LogEventHandler = delegate { }; + + /// + /// Default log event handling + /// + public void HandleLogEvent(object sender, LogEventArgs args) + { + // Null args means we can't handle it + if (args == null) + return; + + // If we have an exception and we're throwing on that + if (ThrowOnError && args.Exception != null) + throw args.Exception; + + // If we have a warning or error, set the flags accordingly + if (args.LogLevel == LogLevel.WARNING) + LoggedWarnings = true; + if (args.LogLevel == LogLevel.ERROR) + LoggedErrors = true; + + // Setup the statement based on the inputs + string logLine; + if (args.Exception == null) + { + logLine = args.Statement ?? string.Empty; + } + else if (args.TotalCount != null && args.CurrentCount != null) + { + double percentage = (args.CurrentCount.Value / args.TotalCount.Value) * 100; + logLine = $"{percentage:N2}%{(args.Statement != null ? ": " + args.Statement : string.Empty)}"; + } + else + { + logLine = $"{(args.Statement != null ? args.Statement + ": " : string.Empty)}{args.Exception}"; + } + + // Then write to the log + Log(logLine, args.LogLevel); + } + /// /// Write the given string to the log output /// /// String to be written log /// Severity of the information being logged - /// True if the level and datetime should be prepended to each statement, false otherwise /// True if the output could be written, false otherwise - private bool Log(string output, LogLevel loglevel, bool appendPrefix) + private bool Log(string output, LogLevel loglevel) { // If the log level is less than the filter level, we skip it but claim we didn't if (loglevel < LowestLogLevel) @@ -198,16 +262,16 @@ namespace SabreTools.Library.Logging // USER and ERROR writes to console if (loglevel == LogLevel.USER || loglevel == LogLevel.ERROR) - Console.WriteLine((loglevel == LogLevel.ERROR && appendPrefix ? loglevel.ToString() + " " : string.Empty) + output); + Console.WriteLine((loglevel == LogLevel.ERROR && this.AppendPrefix ? loglevel.ToString() + " " : string.Empty) + output); // If we're writing to file, use the existing stream if (LogToFile) { try { - lock(_lock) + lock (_lock) { - _log.WriteLine((appendPrefix ? $"{loglevel} - {DateTime.Now} - " : string.Empty) + output); + _log.WriteLine((this.AppendPrefix ? $"{loglevel} - {DateTime.Now} - " : string.Empty) + output); } } catch (Exception ex) @@ -222,67 +286,29 @@ namespace SabreTools.Library.Logging return true; } - /// - /// Write the given exact string to the log output - /// - /// String to be written log - /// Line number to write out to - /// Column number to write out to - /// True if the output could be written, false otherwise - public bool WriteExact(string output, int line, int column) - { - // Set the cursor position (if not being redirected) - if (!Console.IsOutputRedirected) - { - Console.CursorTop = line; - Console.CursorLeft = column; - } + #endregion - // Write out to the console - Console.Write(output); - - // If we're writing to file, use the existing stream - if (LogToFile) - { - try - { - lock (_lock) - { - _log.Write($"{DateTime.Now} - {output}"); - } - } - catch - { - Console.WriteLine("Could not write to log file!"); - return false; - } - } - - return true; - } + #region Log Event Triggers /// /// Write the given exception as a verbose message to the log output /// /// Exception to be written log /// String to be written log - /// True if the level and datetime should be prepended to each statement (default), false otherwise /// True if the output could be written, false otherwise - public bool Verbose(Exception ex, string output = null, bool appendPrefix = true) + public void Verbose(Exception ex, string output = null) { - if (ThrowOnError) throw ex; - return Verbose($"{(output != null ? output + ": " : string.Empty)}{ex}", appendPrefix); + LogEventHandler(this, new LogEventArgs(LogLevel.VERBOSE, output, ex)); } /// /// Write the given string as a verbose message to the log output /// /// String to be written log - /// True if the level and datetime should be prepended to each statement (default), false otherwise /// True if the output could be written, false otherwise - public bool Verbose(string output, bool appendPrefix = true) + public void Verbose(string output) { - return Log(output, LogLevel.VERBOSE, appendPrefix); + LogEventHandler(this, new LogEventArgs(LogLevel.VERBOSE, output, null)); } /// @@ -290,23 +316,20 @@ namespace SabreTools.Library.Logging /// /// Exception to be written log /// String to be written log - /// True if the level and datetime should be prepended to each statement (default), false otherwise /// True if the output could be written, false otherwise - public bool User(Exception ex, string output = null, bool appendPrefix = true) + public void User(Exception ex, string output = null) { - if (ThrowOnError) throw ex; - return User($"{(output != null ? output + ": " : string.Empty)}{ex}", appendPrefix); + LogEventHandler(this, new LogEventArgs(LogLevel.USER, output, ex)); } /// /// Write the given string as a user message to the log output /// /// String to be written log - /// True if the level and datetime should be prepended to each statement (default), false otherwise /// True if the output could be written, false otherwise - public bool User(string output, bool appendPrefix = true) + public void User(string output) { - return Log(output, LogLevel.USER, appendPrefix); + LogEventHandler(this, new LogEventArgs(LogLevel.USER, output, null)); } /// @@ -314,24 +337,20 @@ namespace SabreTools.Library.Logging /// /// Exception to be written log /// String to be written log - /// True if the level and datetime should be prepended to each statement (default), false otherwise /// True if the output could be written, false otherwise - public bool Warning(Exception ex, string output = null, bool appendPrefix = true) + public void Warning(Exception ex, string output = null) { - if (ThrowOnError) throw ex; - return Warning($"{(output != null ? output + ": " : string.Empty)}{ex}", appendPrefix); + LogEventHandler(this, new LogEventArgs(LogLevel.WARNING, output, ex)); } /// /// Write the given string as a warning to the log output /// /// String to be written log - /// True if the level and datetime should be prepended to each statement (default), false otherwise /// True if the output could be written, false otherwise - public bool Warning(string output, bool appendPrefix = true) + public void Warning(string output) { - LoggedWarnings = true; - return Log(output, LogLevel.WARNING, appendPrefix); + LogEventHandler(this, new LogEventArgs(LogLevel.WARNING, output, null)); } /// @@ -339,44 +358,22 @@ namespace SabreTools.Library.Logging /// /// Exception to be written log /// String to be written log - /// True if the level and datetime should be prepended to each statement (default), false otherwise /// True if the output could be written, false otherwise - public bool Error(Exception ex, string output = null, bool appendPrefix = true) + public void Error(Exception ex, string output = null) { - if (ThrowOnError) throw ex; - return Error($"{(output != null ? output + ": " : string.Empty)}{ex}", appendPrefix); + LogEventHandler(this, new LogEventArgs(LogLevel.ERROR, output, ex)); } /// /// Writes the given string as an error in the log /// /// String to be written log - /// True if the level and datetime should be prepended to each statement (default), false otherwise /// True if the output could be written, false otherwise - public bool Error(string output, bool appendPrefix = true) + public void Error(string output) { - LoggedErrors = true; - return Log(output, LogLevel.ERROR, appendPrefix); + LogEventHandler(this, new LogEventArgs(LogLevel.ERROR, output, null)); } - /// - /// Clear lines beneath the given line in the console - /// - /// Line number to clear beneath - /// True - public bool ClearBeneath(int line) - { - if (!Console.IsOutputRedirected) - { - for (int i = line; i < Console.WindowHeight; i++) - { - // http://stackoverflow.com/questions/8946808/can-console-clear-be-used-to-only-clear-a-line-instead-of-whole-console - Console.SetCursorPosition(0, Console.CursorTop); - Console.Write(new string(' ', Console.WindowWidth)); - Console.SetCursorPosition(0, i); - } - } - return true; - } + #endregion } } diff --git a/SabreTools/Program.cs b/SabreTools/Program.cs index 9ba94502..8ceca0f7 100644 --- a/SabreTools/Program.cs +++ b/SabreTools/Program.cs @@ -20,7 +20,12 @@ namespace SabreTools public static void Main(string[] args) { // Perform initial setup and verification - Globals.Logger = new Logger("sabretools.log"); + Globals.Logger = new Logger("sabretools.log") + { + AppendPrefix = true, + LowestLogLevel = LogLevel.VERBOSE, + ThrowOnError = false, + }; // Create a new Help object for this program _help = RetrieveHelp();