mfgames-cil/src/MfGames.ToolBuilder/Logging/LoggingToolService.cs
D. Moonfire d2acd5a3c7
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
feat!: removed the Spectre.Console formatter from default
2023-09-02 14:08:24 -05:00

206 lines
5.6 KiB
C#

using System.CommandLine;
using MfGames.ToolBuilder.Extensions;
using Serilog;
using Serilog.Core;
using Serilog.Events;
using Serilog.Exceptions;
using Serilog.Templates;
using Serilog.Templates.Themes;
namespace MfGames.ToolBuilder.Logging;
/// <summary>
/// A service for handling logging options.
/// </summary>
public class LoggingToolService
{
public LoggingToolService()
{
this.LogLevelOption = new Option<string>(
"--log-level",
() => nameof(LogEventLevel.Warning),
string.Format(
"Controls the verbosity of the output, not case-sensitive and prefixes allowed: {0}",
string.Join(", ", Enum.GetNames<LogEventLevel>())));
this.LogContextOption = new Option<string>(
"--log-context-format",
() => nameof(LogContextFormat.Class),
string.Format(
"Controls the format of the source context for log items, not case-sensitive and prefixes allowed: {0}",
string.Join(", ", Enum.GetNames<LogContextFormat>())));
this.LogTimeFormat = new Option<string>(
"--log-time-format",
() => "HH:mm:ss",
string.Join(
" ",
"Controls the format of the time in the log messages,",
"such as 'HH:mm:ss' (default) or 'yyyy-MM-ddTHH:mm:ss'.",
"Blank means exclude entirely."));
this.LogLevelOverrideOption = new Option<List<string>>(
"--log-level-override",
() => new List<string> { "Microsoft=Warning" },
"Overrides log levels for certain contexts in the format of either 'Context' or 'Context=Level' (repeat for multiple)")
{
Arity = ArgumentArity.OneOrMore,
};
}
public Option<string> LogContextOption { get; set; }
/// <summary>
/// Gets the common option for setting the log level.
/// </summary>
public Option<string> LogLevelOption { get; }
public Option<List<string>> LogLevelOverrideOption { get; set; }
/// <summary>
/// Gets the log time format to use.
/// </summary>
public Option<string> LogTimeFormat { get; set; }
/// <summary>
/// Adds the options to the given command.
/// </summary>
/// <param name="command"></param>
public void Attach(Command command)
{
command.AddOption(this.LogLevelOption);
command.AddOption(this.LogContextOption);
command.AddOption(this.LogTimeFormat);
command.AddOption(this.LogLevelOverrideOption);
}
/// <summary>
/// Adds the common options to the command.
/// </summary>
/// <param name="command"></param>
public void AttachGlobal(Command command)
{
command.AddGlobalOption(this.LogLevelOption);
command.AddGlobalOption(this.LogContextOption);
command.AddGlobalOption(this.LogTimeFormat);
command.AddGlobalOption(this.LogLevelOverrideOption);
}
/// <summary>
/// Sets up logging based on the global settings.
/// </summary>
/// <param name="arguments">The arguments to the command.</param>
public void Configure(string[] arguments)
{
LogEventLevel logLevel = this.GetEventLevel(arguments);
ExpressionTemplate template = this.GetExpressionTemplate(arguments);
Dictionary<string, LogEventLevel> levelOverrides =
this.GetLevelOverrides(arguments);
LoggerConfiguration configuration = new LoggerConfiguration()
.Enrich.WithDemystifiedStackTraces()
.Enrich.WithExceptionDetails()
.MinimumLevel.Is(logLevel);
foreach (KeyValuePair<string, LogEventLevel> pair in levelOverrides)
{
configuration = configuration.MinimumLevel
.Override(pair.Key, pair.Value);
}
configuration = configuration
.Enrich.FromLogContext()
.WriteTo.Console(template);
Logger logger = configuration.CreateLogger();
Log.Logger = logger;
}
private LogContextFormat GetContextFormat(string[] arguments)
{
string level = GlobalOptionHelper.GetArgumentValue(
this.LogContextOption,
arguments,
nameof(LogContextFormat.Class));
LogContextFormat format = level.GetEnumFuzzy<LogContextFormat>(
"log context format");
return format;
}
private LogEventLevel GetEventLevel(string[] arguments)
{
string level = GlobalOptionHelper.GetArgumentValue(
this.LogLevelOption,
arguments,
"Warning");
LogEventLevel logLevel = level.GetEnumFuzzy<LogEventLevel>(
"log level");
return logLevel;
}
private ExpressionTemplate GetExpressionTemplate(string[] arguments)
{
// Pull out the formats.
LogContextFormat contextFormat = this.GetContextFormat(arguments);
string timeFormat = this.GetTimeFormat(arguments);
// Figure out the expression template we want to use.
string timeExpression = string.IsNullOrWhiteSpace(timeFormat)
? ""
: $"{{@t:{timeFormat}}} ";
string contextExpression = contextFormat switch
{
LogContextFormat.None => "",
LogContextFormat.Class =>
"{#if SourceContext is not null} <{Substring(SourceContext, LastIndexOf(SourceContext, '.') + 1)}>{#end}",
LogContextFormat.Full =>
"{#if SourceContext is not null} <{SourceContext}>{#end}",
_ => throw new ArgumentOutOfRangeException(),
};
var template = new ExpressionTemplate(
string.Join(
"",
$"[{timeExpression}{{@l:u3}}]",
contextExpression,
" {@m}\n{@x}"),
theme: TemplateTheme.Literate);
return template;
}
private Dictionary<string, LogEventLevel> GetLevelOverrides(
string[] arguments)
{
List<string> levels = GlobalOptionHelper.GetArgumentValue(
this.LogLevelOverrideOption,
arguments,
new List<string> { "Microsoft=Warning" });
return levels
.ToDictionary(
a => a.Split("=", 2)[0],
a => a.Contains("=")
? a.Split("=", 2)[1]
.GetEnumFuzzy<LogEventLevel>(
"log level")
: (LogEventLevel)int.MaxValue);
}
private string GetTimeFormat(string[] arguments)
{
string timeFormat = GlobalOptionHelper.GetArgumentValue(
this.LogTimeFormat,
arguments,
"HH:mm:ss");
return timeFormat;
}
}