Convert logging to be event-based

This commit is contained in:
Matt Nadareski
2020-10-07 13:16:53 -07:00
parent 4d78f8a0c1
commit 4325c0b7ce
6 changed files with 168 additions and 100 deletions

View File

@@ -26,7 +26,12 @@ namespace RombaSharp
public static void Main(string[] args) public static void Main(string[] args)
{ {
// Perform initial setup and verification // 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 // Create a new Help object for this program
_help = RetrieveHelp(); _help = RetrieveHelp();

View File

@@ -1,4 +1,5 @@
using System.Collections; using System;
using System.Collections;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@@ -1410,13 +1411,13 @@ namespace SabreTools.Library.DatFiles
dirStats.ResetStatistics(); dirStats.ResetStatistics();
} }
Globals.Logger.Verbose($"Beginning stat collection for '{file.CurrentPath}'", false); Globals.Logger.Verbose($"Beginning stat collection for '{file.CurrentPath}'");
List<string> games = new List<string>(); List<string> games = new List<string>();
DatFile datdata = DatFile.CreateAndParse(file.CurrentPath); DatFile datdata = DatFile.CreateAndParse(file.CurrentPath);
datdata.Items.BucketBy(Field.Machine_Name, DedupeType.None, norename: true); datdata.Items.BucketBy(Field.Machine_Name, DedupeType.None, norename: true);
// Output single DAT stats (if asked) // 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) if (single)
{ {
reports.ForEach(report => report.ReplaceStatistics(datdata.Header.FileName, datdata.Items.Keys.Count, datdata.Items)); 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 // Output footer if needed
reports.ForEach(report => report.WriteFooter()); reports.ForEach(report => report.WriteFooter());
Globals.Logger.User(@" Globals.Logger.User($"{Environment.NewLine}Please check the log folder if the stats scrolled offscreen");
Please check the log folder if the stats scrolled offscreen", false);
} }
/// <summary> /// <summary>

View File

@@ -12,8 +12,8 @@ namespace SabreTools.Library.Data
/// <summary> /// <summary>
/// The current toolset version to be used by all child applications /// The current toolset version to be used by all child applications
/// </summary> /// </summary>
public readonly static string Version = $"v1.0.4"; //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-{File.GetCreationTime(Assembly.GetExecutingAssembly().Location):yyyy-MM-dd HH:mm:ss}";
public const int HeaderHeight = 3; public const int HeaderHeight = 3;
#region 0-byte file constants #region 0-byte file constants

View File

@@ -0,0 +1,61 @@
using System;
namespace SabreTools.Library.Logging
{
/// <summary>
/// Generic delegate type for log events
/// </summary>
public delegate void LogEventHandler(object sender, LogEventArgs args);
/// <summary>
/// Logging specific event arguments
/// </summary>
public class LogEventArgs : EventArgs
{
/// <summary>
/// LogLevel for the event
/// </summary>
public LogLevel LogLevel { get; set; } = LogLevel.VERBOSE;
/// <summary>
/// Log statement to be printed
/// </summary>
public string Statement { get; set; } = null;
/// <summary>
/// Exception to be passed along to the event handler
/// </summary>
public Exception Exception { get; set; } = null;
/// <summary>
/// Total count for progress log events
/// </summary>
public long? TotalCount { get; set; } = null;
/// <summary>
/// Current count for progress log events
/// </summary>
public long? CurrentCount { get; set; } = null;
/// <summary>
/// Statement and exception constructor
/// </summary>
public LogEventArgs(LogLevel logLevel = LogLevel.VERBOSE, string statement = null, Exception exception = null)
{
this.LogLevel = logLevel;
this.Statement = statement;
this.Exception = exception;
}
/// <summary>
/// Progress constructor
/// </summary>
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;
}
}
}

View File

@@ -35,6 +35,11 @@ namespace SabreTools.Library.Logging
/// </summary> /// </summary>
public LogLevel LowestLogLevel { get; set; } = LogLevel.VERBOSE; public LogLevel LowestLogLevel { get; set; } = LogLevel.VERBOSE;
/// <summary>
/// Determines whether to prefix log lines with level and datetime
/// </summary>
public bool AppendPrefix { get; set; } = true;
/// <summary> /// <summary>
/// Determines whether to throw if an exception is logged /// Determines whether to throw if an exception is logged
/// </summary> /// </summary>
@@ -71,6 +76,8 @@ namespace SabreTools.Library.Logging
#endregion #endregion
#region Constructors
/// <summary> /// <summary>
/// Initialize a console-only logger object /// Initialize a console-only logger object
/// </summary> /// </summary>
@@ -86,6 +93,7 @@ namespace SabreTools.Library.Logging
/// <param name="addDate">True to add a date string to the filename (default), false otherwise</param> /// <param name="addDate">True to add a date string to the filename (default), false otherwise</param>
public Logger(string filename, bool addDate = true) public Logger(string filename, bool addDate = true)
{ {
// Set and create the output
if (addDate) if (addDate)
Filename = $"{Path.GetFileNameWithoutExtension(filename)} ({DateTime.Now:yyyy-MM-dd HH-mm-ss}).{PathExtensions.GetNormalizedExtension(filename)}"; Filename = $"{Path.GetFileNameWithoutExtension(filename)} ({DateTime.Now:yyyy-MM-dd HH-mm-ss}).{PathExtensions.GetNormalizedExtension(filename)}";
else else
@@ -97,16 +105,25 @@ namespace SabreTools.Library.Logging
Start(); Start();
} }
#endregion
#region Control
/// <summary> /// <summary>
/// Start logging by opening output file (if necessary) /// Start logging by opening output file (if necessary)
/// </summary> /// </summary>
/// <returns>True if the logging was started correctly, false otherwise</returns> /// <returns>True if the logging was started correctly, false otherwise</returns>
public bool Start() public bool Start()
{ {
// Setup the logging handler to always use the internal log
LogEventHandler += HandleLogEvent;
// Start the logging
StartTime = DateTime.Now; StartTime = DateTime.Now;
if (!LogToFile) if (!LogToFile)
return true; return true;
// Setup file output and perform initial log
try try
{ {
FileStream logfile = FileExtensions.TryCreate(Path.Combine(LogDirectory, Filename)); FileStream logfile = FileExtensions.TryCreate(Path.Combine(LogDirectory, Filename));
@@ -183,14 +200,61 @@ namespace SabreTools.Library.Logging
return true; return true;
} }
#endregion
#region Event Handling
/// <summary>
/// Handler for log events
/// </summary>
public static event LogEventHandler LogEventHandler = delegate { };
/// <summary>
/// Default log event handling
/// </summary>
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);
}
/// <summary> /// <summary>
/// Write the given string to the log output /// Write the given string to the log output
/// </summary> /// </summary>
/// <param name="output">String to be written log</param> /// <param name="output">String to be written log</param>
/// <param name="loglevel">Severity of the information being logged</param> /// <param name="loglevel">Severity of the information being logged</param>
/// <param name="appendPrefix">True if the level and datetime should be prepended to each statement, false otherwise</param>
/// <returns>True if the output could be written, false otherwise</returns> /// <returns>True if the output could be written, false otherwise</returns>
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 the log level is less than the filter level, we skip it but claim we didn't
if (loglevel < LowestLogLevel) if (loglevel < LowestLogLevel)
@@ -198,16 +262,16 @@ namespace SabreTools.Library.Logging
// USER and ERROR writes to console // USER and ERROR writes to console
if (loglevel == LogLevel.USER || loglevel == LogLevel.ERROR) 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 we're writing to file, use the existing stream
if (LogToFile) if (LogToFile)
{ {
try 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) catch (Exception ex)
@@ -222,67 +286,29 @@ namespace SabreTools.Library.Logging
return true; return true;
} }
/// <summary> #endregion
/// Write the given exact string to the log output
/// </summary>
/// <param name="output">String to be written log</param>
/// <param name="line">Line number to write out to</param>
/// <param name="column">Column number to write out to</param>
/// <returns>True if the output could be written, false otherwise</returns>
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;
}
// Write out to the console #region Log Event Triggers
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;
}
/// <summary> /// <summary>
/// Write the given exception as a verbose message to the log output /// Write the given exception as a verbose message to the log output
/// </summary> /// </summary>
/// <param name="ex">Exception to be written log</param> /// <param name="ex">Exception to be written log</param>
/// <param name="output">String to be written log</param> /// <param name="output">String to be written log</param>
/// <param name="appendPrefix">True if the level and datetime should be prepended to each statement (default), false otherwise</param>
/// <returns>True if the output could be written, false otherwise</returns> /// <returns>True if the output could be written, false otherwise</returns>
public bool Verbose(Exception ex, string output = null, bool appendPrefix = true) public void Verbose(Exception ex, string output = null)
{ {
if (ThrowOnError) throw ex; LogEventHandler(this, new LogEventArgs(LogLevel.VERBOSE, output, ex));
return Verbose($"{(output != null ? output + ": " : string.Empty)}{ex}", appendPrefix);
} }
/// <summary> /// <summary>
/// Write the given string as a verbose message to the log output /// Write the given string as a verbose message to the log output
/// </summary> /// </summary>
/// <param name="output">String to be written log</param> /// <param name="output">String to be written log</param>
/// <param name="appendPrefix">True if the level and datetime should be prepended to each statement (default), false otherwise</param>
/// <returns>True if the output could be written, false otherwise</returns> /// <returns>True if the output could be written, false otherwise</returns>
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));
} }
/// <summary> /// <summary>
@@ -290,23 +316,20 @@ namespace SabreTools.Library.Logging
/// </summary> /// </summary>
/// <param name="ex">Exception to be written log</param> /// <param name="ex">Exception to be written log</param>
/// <param name="output">String to be written log</param> /// <param name="output">String to be written log</param>
/// <param name="appendPrefix">True if the level and datetime should be prepended to each statement (default), false otherwise</param>
/// <returns>True if the output could be written, false otherwise</returns> /// <returns>True if the output could be written, false otherwise</returns>
public bool User(Exception ex, string output = null, bool appendPrefix = true) public void User(Exception ex, string output = null)
{ {
if (ThrowOnError) throw ex; LogEventHandler(this, new LogEventArgs(LogLevel.USER, output, ex));
return User($"{(output != null ? output + ": " : string.Empty)}{ex}", appendPrefix);
} }
/// <summary> /// <summary>
/// Write the given string as a user message to the log output /// Write the given string as a user message to the log output
/// </summary> /// </summary>
/// <param name="output">String to be written log</param> /// <param name="output">String to be written log</param>
/// <param name="appendPrefix">True if the level and datetime should be prepended to each statement (default), false otherwise</param>
/// <returns>True if the output could be written, false otherwise</returns> /// <returns>True if the output could be written, false otherwise</returns>
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));
} }
/// <summary> /// <summary>
@@ -314,24 +337,20 @@ namespace SabreTools.Library.Logging
/// </summary> /// </summary>
/// <param name="ex">Exception to be written log</param> /// <param name="ex">Exception to be written log</param>
/// <param name="output">String to be written log</param> /// <param name="output">String to be written log</param>
/// <param name="appendPrefix">True if the level and datetime should be prepended to each statement (default), false otherwise</param>
/// <returns>True if the output could be written, false otherwise</returns> /// <returns>True if the output could be written, false otherwise</returns>
public bool Warning(Exception ex, string output = null, bool appendPrefix = true) public void Warning(Exception ex, string output = null)
{ {
if (ThrowOnError) throw ex; LogEventHandler(this, new LogEventArgs(LogLevel.WARNING, output, ex));
return Warning($"{(output != null ? output + ": " : string.Empty)}{ex}", appendPrefix);
} }
/// <summary> /// <summary>
/// Write the given string as a warning to the log output /// Write the given string as a warning to the log output
/// </summary> /// </summary>
/// <param name="output">String to be written log</param> /// <param name="output">String to be written log</param>
/// <param name="appendPrefix">True if the level and datetime should be prepended to each statement (default), false otherwise</param>
/// <returns>True if the output could be written, false otherwise</returns> /// <returns>True if the output could be written, false otherwise</returns>
public bool Warning(string output, bool appendPrefix = true) public void Warning(string output)
{ {
LoggedWarnings = true; LogEventHandler(this, new LogEventArgs(LogLevel.WARNING, output, null));
return Log(output, LogLevel.WARNING, appendPrefix);
} }
/// <summary> /// <summary>
@@ -339,44 +358,22 @@ namespace SabreTools.Library.Logging
/// </summary> /// </summary>
/// <param name="ex">Exception to be written log</param> /// <param name="ex">Exception to be written log</param>
/// <param name="output">String to be written log</param> /// <param name="output">String to be written log</param>
/// <param name="appendPrefix">True if the level and datetime should be prepended to each statement (default), false otherwise</param>
/// <returns>True if the output could be written, false otherwise</returns> /// <returns>True if the output could be written, false otherwise</returns>
public bool Error(Exception ex, string output = null, bool appendPrefix = true) public void Error(Exception ex, string output = null)
{ {
if (ThrowOnError) throw ex; LogEventHandler(this, new LogEventArgs(LogLevel.ERROR, output, ex));
return Error($"{(output != null ? output + ": " : string.Empty)}{ex}", appendPrefix);
} }
/// <summary> /// <summary>
/// Writes the given string as an error in the log /// Writes the given string as an error in the log
/// </summary> /// </summary>
/// <param name="output">String to be written log</param> /// <param name="output">String to be written log</param>
/// <param name="appendPrefix">True if the level and datetime should be prepended to each statement (default), false otherwise</param>
/// <returns>True if the output could be written, false otherwise</returns> /// <returns>True if the output could be written, false otherwise</returns>
public bool Error(string output, bool appendPrefix = true) public void Error(string output)
{ {
LoggedErrors = true; LogEventHandler(this, new LogEventArgs(LogLevel.ERROR, output, null));
return Log(output, LogLevel.ERROR, appendPrefix);
} }
/// <summary> #endregion
/// Clear lines beneath the given line in the console
/// </summary>
/// <param name="line">Line number to clear beneath</param>
/// <returns>True</returns>
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;
}
} }
} }

View File

@@ -20,7 +20,12 @@ namespace SabreTools
public static void Main(string[] args) public static void Main(string[] args)
{ {
// Perform initial setup and verification // 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 // Create a new Help object for this program
_help = RetrieveHelp(); _help = RetrieveHelp();