mirror of
https://github.com/claunia/SabreTools.git
synced 2025-12-16 19:14:27 +00:00
Extract out Logging namespace
This commit is contained in:
470
SabreTools.Logging/LoggerImpl.cs
Normal file
470
SabreTools.Logging/LoggerImpl.cs
Normal file
@@ -0,0 +1,470 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
using SabreTools.Data;
|
||||
|
||||
namespace SabreTools.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal logging implementation
|
||||
/// </summary>
|
||||
public static class LoggerImpl
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Optional output filename for logs
|
||||
/// </summary>
|
||||
public static string Filename { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if we're logging to file or not
|
||||
/// </summary>
|
||||
public static bool LogToFile { get { return !string.IsNullOrWhiteSpace(Filename); } }
|
||||
|
||||
/// <summary>
|
||||
/// Optional output log directory
|
||||
/// </summary>
|
||||
/// TODO: Make this either passed in or optional
|
||||
public static string LogDirectory { get; set; } = Path.Combine(Globals.ExeDir, "logs") + Path.DirectorySeparatorChar;
|
||||
|
||||
/// <summary>
|
||||
/// Determines the lowest log level to output
|
||||
/// </summary>
|
||||
public static LogLevel LowestLogLevel { get; set; } = LogLevel.VERBOSE;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether to prefix log lines with level and datetime
|
||||
/// </summary>
|
||||
public static bool AppendPrefix { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether to throw if an exception is logged
|
||||
/// </summary>
|
||||
public static bool ThrowOnError { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Logging start time for metrics
|
||||
/// </summary>
|
||||
public static DateTime StartTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if there were errors logged
|
||||
/// </summary>
|
||||
public static bool LoggedErrors { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if there were warnings logged
|
||||
/// </summary>
|
||||
public static bool LoggedWarnings { get; private set; } = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private variables
|
||||
|
||||
/// <summary>
|
||||
/// StreamWriter representing the output log file
|
||||
/// </summary>
|
||||
private static StreamWriter _log;
|
||||
|
||||
/// <summary>
|
||||
/// Object lock for multithreaded logging
|
||||
/// </summary>
|
||||
private static readonly object _lock = new object();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Control
|
||||
|
||||
/// <summary>
|
||||
/// Generate and set the log filename
|
||||
/// </summary>
|
||||
/// <param name="filename">Base filename to use</param>
|
||||
/// <param name="addDate">True to append a date to the filename, false otherwise</param>
|
||||
public static void SetFilename(string filename, bool addDate = true)
|
||||
{
|
||||
// Set and create the output
|
||||
if (addDate)
|
||||
Filename = $"{Path.GetFileNameWithoutExtension(filename)} ({DateTime.Now:yyyy-MM-dd HH-mm-ss}).{GetNormalizedExtension(filename)}";
|
||||
else
|
||||
Filename = filename;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start logging by opening output file (if necessary)
|
||||
/// </summary>
|
||||
/// <returns>True if the logging was started correctly, false otherwise</returns>
|
||||
public static 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
|
||||
{
|
||||
if (!string.IsNullOrEmpty(LogDirectory) && !Directory.Exists(LogDirectory))
|
||||
Directory.CreateDirectory(LogDirectory);
|
||||
|
||||
FileStream logfile = TryCreate(Path.Combine(LogDirectory, Filename));
|
||||
_log = new StreamWriter(logfile, Encoding.UTF8, (int)(4 * Constants.KibiByte), true)
|
||||
{
|
||||
AutoFlush = true
|
||||
};
|
||||
|
||||
_log.WriteLine($"Logging started {StartTime:yyyy-MM-dd HH:mm:ss}");
|
||||
_log.WriteLine($"Command run: {Globals.CommandLineArgs}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End logging by closing output file (if necessary)
|
||||
/// </summary>
|
||||
/// <param name="suppress">True if all ending output is to be suppressed, false otherwise (default)</param>
|
||||
/// <returns>True if the logging was ended correctly, false otherwise</returns>
|
||||
public static bool Close(bool suppress = false)
|
||||
{
|
||||
if (!suppress)
|
||||
{
|
||||
if (LoggedWarnings)
|
||||
Console.WriteLine("There were warnings in the last run! Check the log for more details");
|
||||
|
||||
if (LoggedErrors)
|
||||
Console.WriteLine("There were errors in the last run! Check the log for more details");
|
||||
|
||||
TimeSpan span = DateTime.Now.Subtract(StartTime);
|
||||
|
||||
// Special case for multi-day runs
|
||||
string total;
|
||||
if (span >= TimeSpan.FromDays(1))
|
||||
total = span.ToString(@"d\:hh\:mm\:ss");
|
||||
else
|
||||
total = span.ToString(@"hh\:mm\:ss");
|
||||
|
||||
if (!LogToFile)
|
||||
{
|
||||
Console.WriteLine($"Total runtime: {total}");
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_log.WriteLine($"Logging ended {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
|
||||
_log.WriteLine($"Total runtime: {total}");
|
||||
Console.WriteLine($"Total runtime: {total}");
|
||||
_log.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_log.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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 static 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 != null ? args.Statement + ": " : string.Empty)}{args.Exception}";
|
||||
}
|
||||
else if (args.TotalCount != null && args.CurrentCount != null)
|
||||
{
|
||||
double percentage = ((double)args.CurrentCount.Value / args.TotalCount.Value) * 100;
|
||||
logLine = $"{percentage:N2}%{(args.Statement != null ? ": " + args.Statement : string.Empty)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
logLine = args.Statement ?? string.Empty;
|
||||
}
|
||||
|
||||
// Then write to the log
|
||||
Log(logLine, args.LogLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the given string to the log output
|
||||
/// </summary>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <param name="loglevel">Severity of the information being logged</param>
|
||||
private static void 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)
|
||||
return;
|
||||
|
||||
// USER and ERROR writes to console
|
||||
if (loglevel == LogLevel.USER || loglevel == LogLevel.ERROR)
|
||||
Console.WriteLine((loglevel == LogLevel.ERROR && AppendPrefix ? loglevel.ToString() + " " : string.Empty) + output);
|
||||
|
||||
// If we're writing to file, use the existing stream
|
||||
if (LogToFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_log.WriteLine((AppendPrefix ? $"{loglevel} - {DateTime.Now} - " : string.Empty) + output);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
Console.WriteLine("Could not write to log file!");
|
||||
if (ThrowOnError) throw ex;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Log Event Triggers
|
||||
|
||||
/// <summary>
|
||||
/// Write the given exception as a verbose message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void Verbose(object instance, Exception ex, string output = null)
|
||||
{
|
||||
LogEventHandler(instance, new LogEventArgs(LogLevel.VERBOSE, output, ex));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the given string as a verbose message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void Verbose(object instance, string output)
|
||||
{
|
||||
LogEventHandler(instance, new LogEventArgs(LogLevel.VERBOSE, output, null));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the given verbose progress message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="total">Total count for progress</param>
|
||||
/// <param name="current">Current count for progres</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
public static void Verbose(object instance, long total, long current, string output = null)
|
||||
{
|
||||
LogEventHandler(instance, new LogEventArgs(total, current, LogLevel.VERBOSE, output));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the given exception as a user message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void User(object instance, Exception ex, string output = null)
|
||||
{
|
||||
LogEventHandler(instance, new LogEventArgs(LogLevel.USER, output, ex));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the given string as a user message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void User(object instance, string output)
|
||||
{
|
||||
LogEventHandler(instance, new LogEventArgs(LogLevel.USER, output, null));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the given user progress message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="total">Total count for progress</param>
|
||||
/// <param name="current">Current count for progres</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
public static void User(object instance, long total, long current, string output = null)
|
||||
{
|
||||
LogEventHandler(instance, new LogEventArgs(total, current, LogLevel.USER, output));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the given exception as a warning to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void Warning(object instance, Exception ex, string output = null)
|
||||
{
|
||||
LogEventHandler(instance, new LogEventArgs(LogLevel.WARNING, output, ex));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the given string as a warning to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void Warning(object instance, string output)
|
||||
{
|
||||
LogEventHandler(instance, new LogEventArgs(LogLevel.WARNING, output, null));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the given warning progress message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="total">Total count for progress</param>
|
||||
/// <param name="current">Current count for progres</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
public static void Warning(object instance, long total, long current, string output = null)
|
||||
{
|
||||
LogEventHandler(instance, new LogEventArgs(total, current, LogLevel.WARNING, output));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given exception as an error in the log
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="ex">Exception to be written log</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void Error(object instance, Exception ex, string output = null)
|
||||
{
|
||||
LogEventHandler(instance, new LogEventArgs(LogLevel.ERROR, output, ex));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given string as an error in the log
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
/// <returns>True if the output could be written, false otherwise</returns>
|
||||
public static void Error(object instance, string output)
|
||||
{
|
||||
LogEventHandler(instance, new LogEventArgs(LogLevel.ERROR, output, null));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the given error progress message to the log output
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance object that's the source of logging</param>
|
||||
/// <param name="total">Total count for progress</param>
|
||||
/// <param name="current">Current count for progres</param>
|
||||
/// <param name="output">String to be written log</param>
|
||||
public static void Error(object instance, long total, long current, string output = null)
|
||||
{
|
||||
LogEventHandler(instance, new LogEventArgs(total, current, LogLevel.ERROR, output));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: Remove this region once IO namespace is separated out properly
|
||||
#region TEMPORARY - REMOVEME
|
||||
|
||||
/// <summary>
|
||||
/// Get the extension from the path, if possible
|
||||
/// </summary>
|
||||
/// <param name="path">Path to get extension from</param>
|
||||
/// <returns>Extension, if possible</returns>
|
||||
public static string GetNormalizedExtension(string path)
|
||||
{
|
||||
// Check null or empty first
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return null;
|
||||
|
||||
// Get the extension from the path, if possible
|
||||
string ext = Path.GetExtension(path)?.ToLowerInvariant();
|
||||
|
||||
// Check if the extension is null or empty
|
||||
if (string.IsNullOrWhiteSpace(ext))
|
||||
return null;
|
||||
|
||||
// Make sure that extensions are valid
|
||||
ext = ext.TrimStart('.');
|
||||
|
||||
return ext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to create a file for write, optionally throwing the error
|
||||
/// </summary>
|
||||
/// <param name="file">Name of the file to create</param>
|
||||
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
|
||||
/// <returns>An opened stream representing the file on success, null otherwise</returns>
|
||||
public static FileStream TryCreate(string file, bool throwOnError = false)
|
||||
{
|
||||
// Now wrap opening the file
|
||||
try
|
||||
{
|
||||
return File.Open(file, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw ex;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user