feat!: reworked the logging commands for the tool

- Added many parameters to control formatting
- Added the ability to exclude source contexts
- Extracted the SpectreConsole expression loggger into a separate assembly
This commit is contained in:
D. Moonfire 2023-07-22 11:32:58 -05:00
parent 6c05266005
commit 770edf99a1
14 changed files with 951 additions and 732 deletions

View file

@ -83,6 +83,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{F7
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleTool", "examples\SampleTool\SampleTool.csproj", "{D58365E6-E98B-4A04-8447-4B9417850D85}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Serilog.SpectreExpressions", "src\MfGames.Serilog.SpectreExpressions\MfGames.Serilog.SpectreExpressions.csproj", "{25457946-9CD0-498E-8B46-03C420CCF103}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -540,6 +542,18 @@ Global
{D58365E6-E98B-4A04-8447-4B9417850D85}.Release|x64.Build.0 = Release|Any CPU
{D58365E6-E98B-4A04-8447-4B9417850D85}.Release|x86.ActiveCfg = Release|Any CPU
{D58365E6-E98B-4A04-8447-4B9417850D85}.Release|x86.Build.0 = Release|Any CPU
{25457946-9CD0-498E-8B46-03C420CCF103}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{25457946-9CD0-498E-8B46-03C420CCF103}.Debug|Any CPU.Build.0 = Debug|Any CPU
{25457946-9CD0-498E-8B46-03C420CCF103}.Debug|x64.ActiveCfg = Debug|Any CPU
{25457946-9CD0-498E-8B46-03C420CCF103}.Debug|x64.Build.0 = Debug|Any CPU
{25457946-9CD0-498E-8B46-03C420CCF103}.Debug|x86.ActiveCfg = Debug|Any CPU
{25457946-9CD0-498E-8B46-03C420CCF103}.Debug|x86.Build.0 = Debug|Any CPU
{25457946-9CD0-498E-8B46-03C420CCF103}.Release|Any CPU.ActiveCfg = Release|Any CPU
{25457946-9CD0-498E-8B46-03C420CCF103}.Release|Any CPU.Build.0 = Release|Any CPU
{25457946-9CD0-498E-8B46-03C420CCF103}.Release|x64.ActiveCfg = Release|Any CPU
{25457946-9CD0-498E-8B46-03C420CCF103}.Release|x64.Build.0 = Release|Any CPU
{25457946-9CD0-498E-8B46-03C420CCF103}.Release|x86.ActiveCfg = Release|Any CPU
{25457946-9CD0-498E-8B46-03C420CCF103}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5253E2A6-9565-45AF-92EA-1BFD3A63AC23} = {9C845D9A-B359-43B3-AE9E-B84CE945AF21}
@ -579,5 +593,6 @@ Global
{80EC7EB3-AC5C-4A23-A1BA-BCB6AD5A36B3} = {4CE102F8-5C70-4696-B85F-93BB10034918}
{36F37E51-E760-4368-B71A-BB2A9C82736A} = {4CE102F8-5C70-4696-B85F-93BB10034918}
{D58365E6-E98B-4A04-8447-4B9417850D85} = {F79B6838-B175-43A3-8C52-69A414CC1386}
{25457946-9CD0-498E-8B46-03C420CCF103} = {9C845D9A-B359-43B3-AE9E-B84CE945AF21}
EndGlobalSection
EndGlobal

View file

@ -10,77 +10,86 @@ namespace SampleTool;
public class LogCommand : Command, ICommandHandler
{
private readonly ILogger<LogCommand> extensionLogger;
private readonly ILogger<LogCommand> extensionLogger;
private readonly ILogger serilogLogger;
private readonly ILogger serilogContextLogger;
/// <inheritdoc />
public LogCommand(
ILoggerFactory loggerFactory,
ILogger serilogLogger)
: base("log", "Shows various logging messages using Serilog and Microsoft")
{
this.serilogLogger = serilogLogger;
this.extensionLogger = loggerFactory.CreateLogger<LogCommand>();
this.Handler = this;
}
private readonly ILogger serilogLogger;
/// <inheritdoc />
public int Invoke(InvocationContext context)
{
return this.InvokeAsync(context).Result;
}
/// <inheritdoc />
public LogCommand(
ILoggerFactory loggerFactory,
ILogger serilogLogger)
: base(
"log",
"Shows various logging messages using Serilog and Microsoft")
{
this.serilogLogger = serilogLogger;
this.serilogContextLogger = serilogLogger.ForContext<LogCommand>();
this.extensionLogger = loggerFactory.CreateLogger<LogCommand>();
this.Handler = this;
}
/// <inheritdoc />
public Task<int> InvokeAsync(InvocationContext context)
{
// Show the serilog logging.
this.serilogLogger.Verbose("Serilog Verbose()");
this.serilogLogger.Debug("Serilog Debug()");
this.serilogLogger.Information("Serilog Information()");
this.serilogLogger.Warning("Serilog Warning()");
this.serilogLogger.Error("Serilog Error()");
this.serilogLogger.Fatal("Serilog Fatal()");
/// <inheritdoc />
public int Invoke(InvocationContext context)
{
return this.InvokeAsync(context).Result;
}
// Show the extension logging.
this.extensionLogger.LogTrace(
"System.Extension.Logging LogTrace");
this.extensionLogger.LogDebug(
"System.Extension.Logging LogDebug");
this.extensionLogger.LogInformation(
"System.Extension.Logging LogInformation");
this.extensionLogger.LogWarning(
"System.Extension.Logging LogWarning");
this.extensionLogger.LogError(
"System.Extension.Logging LogError");
this.extensionLogger.LogCritical(
"System.Extension.Logging LogCritical");
/// <inheritdoc />
public Task<int> InvokeAsync(InvocationContext context)
{
// Show the serilog logging.
this.serilogLogger.Verbose("Serilog Verbose()");
this.serilogLogger.Debug("Serilog Debug()");
this.serilogLogger.Information("Serilog Information()");
this.serilogLogger.Warning("Serilog Warning()");
this.serilogLogger.Error("Serilog Error()");
this.serilogLogger.Fatal("Serilog Fatal()");
// Show Serilog working through logging extensions.
var hash = new
{
Number = 1,
String = "String",
Inner = new { Nested = true }
};
// Show serilog with context.
this.serilogContextLogger.Information(
"Serilog Information() with context");
this.serilogLogger.Information(
"Serilog Contextual parameters {Name} and {Quotes:l}",
"extension logger",
"without quotes");
this.serilogLogger.Information(
"Serilog Contextual nested object {@Nested}",
hash);
// Show the extension logging.
this.extensionLogger.LogTrace(
"System.Extension.Logging LogTrace");
this.extensionLogger.LogDebug(
"System.Extension.Logging LogDebug");
this.extensionLogger.LogInformation(
"System.Extension.Logging LogInformation");
this.extensionLogger.LogWarning(
"System.Extension.Logging LogWarning");
this.extensionLogger.LogError(
"System.Extension.Logging LogError");
this.extensionLogger.LogCritical(
"System.Extension.Logging LogCritical");
this.extensionLogger.LogInformation(
"System.Extension.Logging parameters {Name} and {Quotes:l}",
"extension logger",
"without quotes");
this.extensionLogger.LogInformation(
"System.Extension.Logging nested object {@Nested}",
hash);
// Show Serilog working through logging extensions.
var hash = new
{
Number = 1,
String = "String",
Inner = new { Nested = true }
};
// We're good.
return Task.FromResult(0);
}
this.serilogLogger.Information(
"Serilog Contextual parameters {Name} and {Quotes:l}",
"extension logger",
"without quotes");
this.serilogLogger.Information(
"Serilog Contextual nested object {@Nested}",
hash);
this.extensionLogger.LogInformation(
"System.Extension.Logging parameters {Name} and {Quotes:l}",
"extension logger",
"without quotes");
this.extensionLogger.LogInformation(
"System.Extension.Logging nested object {@Nested}",
hash);
// We're good.
return Task.FromResult(0);
}
}

View file

@ -0,0 +1,539 @@
using System.Text;
using Serilog.Core;
using Serilog.Events;
using Serilog.Templates;
using Spectre.Console;
namespace MfGames.Serilog.SpectreExpressions;
public class ExpressionSpectreConsoleSink : ILogEventSink
{
/// <summary>
/// The internal lookup codes for ANSI colors to Spectre color names.
/// https://spectreconsole.net/appendix/colors
/// </summary>
private static readonly Dictionary<string, string> AnsiToSpectre = new()
{
["0"] = "black",
["1"] = "maroon",
["2"] = "green",
["3"] = "olive",
["4"] = "navy",
["5"] = "purple",
["6"] = "teal",
["7"] = "silver",
["8"] = "grey",
["9"] = "red",
["10"] = "lime",
["11"] = "yellow",
["12"] = "blue",
["13"] = "fuchsia",
["14"] = "aqua",
["15"] = "white",
["16"] = "grey0",
["17"] = "navyblue",
["18"] = "darkblue",
["19"] = "blue3",
["20"] = "blue3_1",
["21"] = "blue1",
["22"] = "darkgreen",
["23"] = "deepskyblue4",
["24"] = "deepskyblue4_1",
["25"] = "deepskyblue4_2",
["26"] = "dodgerblue3",
["27"] = "dodgerblue2",
["28"] = "green4",
["29"] = "springgreen4",
["30"] = "turquoise4",
["31"] = "deepskyblue3",
["32"] = "deepskyblue3_1",
["33"] = "dodgerblue1",
["34"] = "green3",
["35"] = "springgreen3",
["36"] = "darkcyan",
["37"] = "lightseagreen",
["38"] = "deepskyblue2",
["39"] = "deepskyblue1",
["40"] = "green3_1",
["41"] = "springgreen3_1",
["42"] = "springgreen2",
["43"] = "cyan3",
["44"] = "darkturquoise",
["45"] = "turquoise2",
["46"] = "green1",
["47"] = "springgreen2_1",
["48"] = "springgreen1",
["49"] = "mediumspringgreen",
["50"] = "cyan2",
["51"] = "cyan1",
["52"] = "darkred",
["53"] = "deeppink4",
["54"] = "purple4",
["55"] = "purple4_1",
["56"] = "purple3",
["57"] = "blueviolet",
["58"] = "orange4",
["59"] = "grey37",
["60"] = "mediumpurple4",
["61"] = "slateblue3",
["62"] = "slateblue3_1",
["63"] = "royalblue1",
["64"] = "chartreuse4",
["65"] = "darkseagreen4",
["66"] = "paleturquoise4",
["67"] = "steelblue",
["68"] = "steelblue3",
["69"] = "cornflowerblue",
["70"] = "chartreuse3",
["71"] = "darkseagreen4_1",
["72"] = "cadetblue",
["73"] = "cadetblue_1",
["74"] = "skyblue3",
["75"] = "steelblue1",
["76"] = "chartreuse3_1",
["77"] = "palegreen3",
["78"] = "seagreen3",
["79"] = "aquamarine3",
["80"] = "mediumturquoise",
["81"] = "steelblue1_1",
["82"] = "chartreuse2",
["83"] = "seagreen2",
["84"] = "seagreen1",
["85"] = "seagreen1_1",
["86"] = "aquamarine1",
["87"] = "darkslategray2",
["88"] = "darkred_1",
["89"] = "deeppink4_1",
["90"] = "darkmagenta",
["91"] = "darkmagenta_1",
["92"] = "darkviolet",
["93"] = "purple_1",
["94"] = "orange4_1",
["95"] = "lightpink4",
["96"] = "plum4",
["97"] = "mediumpurple3",
["98"] = "mediumpurple3_1",
["99"] = "slateblue1",
["100"] = "yellow4",
["101"] = "wheat4",
["102"] = "grey53",
["103"] = "lightslategrey",
["104"] = "mediumpurple",
["105"] = "lightslateblue",
["106"] = "yellow4_1",
["107"] = "darkolivegreen3",
["108"] = "darkseagreen",
["109"] = "lightskyblue3",
["110"] = "lightskyblue3_1",
["111"] = "skyblue2",
["112"] = "chartreuse2_1",
["113"] = "darkolivegreen3_1",
["114"] = "palegreen3_1",
["115"] = "darkseagreen3",
["116"] = "darkslategray3",
["117"] = "skyblue1",
["118"] = "chartreuse1",
["119"] = "lightgreen",
["120"] = "lightgreen_1",
["121"] = "palegreen1",
["122"] = "aquamarine1_1",
["123"] = "darkslategray1",
["124"] = "red3",
["125"] = "deeppink4_2",
["126"] = "mediumvioletred",
["127"] = "magenta3",
["128"] = "darkviolet_1",
["129"] = "purple_2",
["130"] = "darkorange3",
["131"] = "indianred",
["132"] = "hotpink3",
["133"] = "mediumorchid3",
["134"] = "mediumorchid",
["135"] = "mediumpurple2",
["136"] = "darkgoldenrod",
["137"] = "lightsalmon3",
["138"] = "rosybrown",
["139"] = "grey63",
["140"] = "mediumpurple2_1",
["141"] = "mediumpurple1",
["142"] = "gold3",
["143"] = "darkkhaki",
["144"] = "navajowhite3",
["145"] = "grey69",
["146"] = "lightsteelblue3",
["147"] = "lightsteelblue",
["148"] = "yellow3",
["149"] = "darkolivegreen3_2",
["150"] = "darkseagreen3_1",
["151"] = "darkseagreen2",
["152"] = "lightcyan3",
["153"] = "lightskyblue1",
["154"] = "greenyellow",
["155"] = "darkolivegreen2",
["156"] = "palegreen1_1",
["157"] = "darkseagreen2_1",
["158"] = "darkseagreen1",
["159"] = "paleturquoise1",
["160"] = "red3_1",
["161"] = "deeppink3",
["162"] = "deeppink3_1",
["163"] = "magenta3_1",
["164"] = "magenta3_2",
["165"] = "magenta2",
["166"] = "darkorange3_1",
["167"] = "indianred_1",
["168"] = "hotpink3_1",
["169"] = "hotpink2",
["170"] = "orchid",
["171"] = "mediumorchid1",
["172"] = "orange3",
["173"] = "lightsalmon3_1",
["174"] = "lightpink3",
["175"] = "pink3",
["176"] = "plum3",
["177"] = "violet",
["178"] = "gold3_1",
["179"] = "lightgoldenrod3",
["180"] = "tan",
["181"] = "mistyrose3",
["182"] = "thistle3",
["183"] = "plum2",
["184"] = "yellow3_1",
["185"] = "khaki3",
["186"] = "lightgoldenrod2",
["187"] = "lightyellow3",
["188"] = "grey84",
["189"] = "lightsteelblue1",
["190"] = "yellow2",
["191"] = "darkolivegreen1",
["192"] = "darkolivegreen1_1",
["193"] = "darkseagreen1_1",
["194"] = "honeydew2",
["195"] = "lightcyan1",
["196"] = "red1",
["197"] = "deeppink2",
["198"] = "deeppink1",
["199"] = "deeppink1_1",
["200"] = "magenta2_1",
["201"] = "magenta1",
["202"] = "orangered1",
["203"] = "indianred1",
["204"] = "indianred1_1",
["205"] = "hotpink",
["206"] = "hotpink_1",
["207"] = "mediumorchid1_1",
["208"] = "darkorange",
["209"] = "salmon1",
["210"] = "lightcoral",
["211"] = "palevioletred1",
["212"] = "orchid2",
["213"] = "orchid1",
["214"] = "orange1",
["215"] = "sandybrown",
["216"] = "lightsalmon1",
["217"] = "lightpink1",
["218"] = "pink1",
["219"] = "plum1",
["220"] = "gold1",
["221"] = "lightgoldenrod2_1",
["222"] = "lightgoldenrod2_2",
["223"] = "navajowhite1",
["224"] = "mistyrose1",
["225"] = "thistle1",
["226"] = "yellow1",
["227"] = "lightgoldenrod1",
["228"] = "khaki1",
["229"] = "wheat1",
["230"] = "cornsilk1",
["231"] = "grey100",
["232"] = "grey3",
["233"] = "grey7",
["234"] = "grey11",
["235"] = "grey15",
["236"] = "grey19",
["237"] = "grey23",
["238"] = "grey27",
["239"] = "grey30",
["240"] = "grey35",
["241"] = "grey39",
["242"] = "grey42",
["243"] = "grey46",
["244"] = "grey50",
["245"] = "grey54",
["246"] = "grey58",
["247"] = "grey62",
["248"] = "grey66",
["249"] = "grey70",
["250"] = "grey74",
["251"] = "grey78",
["252"] = "grey82",
["253"] = "grey85",
["254"] = "grey89",
["255"] = "grey93",
};
private readonly ExpressionTemplate template;
public ExpressionSpectreConsoleSink(ExpressionTemplate template)
{
this.template = template;
}
public void Emit(LogEvent logEvent)
{
// SpectreConsole cannot handle the ANSI codes that Serilog.Expressions
// generates, but Serilog.Expressions provides no mechanism for swapping
// out the ANSI for the tags that SpectreConsole uses. To get around
// this, we use a hack that parses the expected ANSI codes from
// Expressions and then convert each one into SpectreConsole's tags
// which will then be turned *back* into ANSI codes by that library.
//
// It is frustrating when popular libraries don't give the ability to
// override things like this.
// Start by having Expressions write out the ANSI-formatted code to a
// string so we can loop through it.
StringWriter writer = new();
this.template.Format(logEvent, writer);
string output = writer.ToString();
// Loop through the output and build up a new markup file that uses
// the SpectreConsole codes instead.
StringBuilder markup = new();
bool inTag = false;
for (int i = 0; i < output.Length; i++)
{
// If we aren't an escape character, escape any required fields and
// then add it to the buffer.
char c = output[i];
if (c != '\x1b')
{
// Add in the character, then escape the specials if needed.
markup.Append(output[i]);
if (output[i] is '[' or ']')
{
markup.Append(output[i]);
}
continue;
}
// For escape codes, we want to grab all the escape codes in a
// series so we can do the correct foreground and background color
// codes in a single markup.
List<string> codes = new();
while (output[i] == '\x1b')
{
StringBuilder escape = new();
i += 2;
while (output[i] != 'm')
{
escape.Append(output[i]);
i++;
}
codes.Add(escape.ToString());
}
// Parse the output without the trailing `m` and the escape code.
string spectreMarkup =
this.TranslateAnsiToSpectre(codes, ref inTag);
markup.Append(spectreMarkup);
}
// Send the modified code to SpectreConsole.
markup.Append(Finalize(ref inTag));
AnsiConsole.Markup(markup.ToString());
}
private static string Finalize(ref bool inTag)
{
return GetMarkup("default", "", "", ref inTag);
}
private static string GetColor256(string index)
{
return AnsiToSpectre.TryGetValue(index.TrimStart('0'), out string? name)
? name
: "";
}
private static string GetMarkup(
string foreground,
string background,
string bold,
ref bool inTag)
{
StringBuilder markup = new();
if (inTag)
{
inTag = false;
markup.Append("[/]");
}
if (foreground != "default"
|| background.Length != 0
|| bold.Length != 0)
{
inTag = true;
markup.AppendFormat("[{0}{1}{2}]", bold, foreground, background);
}
return markup.ToString();
}
private string TranslateAnsiToSpectre(List<string> codes, ref bool inTag)
{
// Build up the markup to give by going through the codes and seeing if
// we have to combine everything together.
StringBuilder markup = new();
string bold = "";
string foreground = "default";
string background = "";
foreach (string code in codes)
{
// This will be a semicolon separated list of parameters.
string[] args = code.Split(';');
switch (args[0])
{
case "0": // Reset
markup.Append(
GetMarkup(foreground, background, bold, ref inTag));
break;
case "38": // Foreground Color
switch (args[1])
{
case "5": // 256 color
foreground = GetColor256(args[2]);
break;
}
break;
case "48": // Background color
switch (args[1])
{
case "5": // 256 color
background = " on " + GetColor256(args[2]);
break;
}
break;
default: // Unknown
// If we have more than one, then treat them as individual
// codes.
//
// https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
foreach (string inner in args)
{
switch (inner)
{
case "1": // Bold
bold = "bold ";
break;
case "30":
foreground = "black";
break;
case "31":
foreground = "red";
break;
case "32":
foreground = "green";
break;
case "33":
foreground = "yellow";
break;
case "34":
foreground = "blue";
break;
case "35":
foreground = "magenta";
break;
case "36":
foreground = "cyan";
break;
case "37":
foreground = "white";
break;
case "39":
foreground = "default";
break;
case "40":
background = " on black";
break;
case "41":
background = " on red";
break;
case "42":
background = " on green";
break;
case "43":
background = " on yellow";
break;
case "44":
background = " on blue";
break;
case "45":
background = " on magenta";
break;
case "46":
background = " on cyan";
break;
case "47":
background = " on white";
break;
case "49":
background = " on default";
break;
default:
break;
}
}
break;
}
// Finish up the codes.
markup.Append(GetMarkup(foreground, background, bold, ref inTag));
}
// Return the resulting markup.
return markup.ToString();
}
}

View file

@ -0,0 +1,50 @@
using Serilog;
using Serilog.Configuration;
using Serilog.Core;
using Serilog.Events;
using Serilog.Templates;
using Serilog.Templates.Themes;
namespace MfGames.Serilog.SpectreExpressions;
public static class ExpressionSpectreConsoleSinkExtensions
{
private const string DefaultConsoleOutputTemplate = "[{@t:HH:mm:ss} {@l:u3}] {@m}\n{@x}";
/// <summary>
/// Write log events to the console using Spectre.Console.
/// </summary>
/// <param name="loggerConfiguration">Logger sink configuration.</param>
/// <param name="outputTemplate">
/// A message template describing the format used to write to the sink.
/// The default is "[{Timestamp:HH:mm:ss} {Level:u3}]
/// {Message:lj}{NewLine}{Exception}".
/// </param>
/// <param name="restrictedToMinimumLevel">
/// The minimum level for
/// events passed through the sink. Ignored when <paramref name="levelSwitch" /> is
/// specified.
/// </param>
/// <param name="levelSwitch">
/// A switch allowing the pass-through minimum level
/// to be changed at runtime.
/// </param>
/// <param name="theme">The Serilog theme for the output.</param>
/// <returns>Configuration object allowing method chaining.</returns>
public static LoggerConfiguration SpectreExpression(
this LoggerSinkConfiguration loggerConfiguration,
ExpressionTemplate? outputTemplate = null,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
LoggingLevelSwitch? levelSwitch = null,
TemplateTheme? theme = null)
{
outputTemplate ??= new ExpressionTemplate(
DefaultConsoleOutputTemplate,
theme: theme);
return loggerConfiguration.Sink(
new ExpressionSpectreConsoleSink(outputTemplate),
restrictedToMinimumLevel,
levelSwitch);
}
}

View file

@ -0,0 +1,13 @@
mode: ContinuousDelivery
increment: Inherit
continuous-delivery-fallback-tag: ci
major-version-bump-message: "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\\([\\w\\s-]*\\))?(!:|:.*\\n\\n((.+\\n)+\\n)?BREAKING CHANGE:\\s.+)"
minor-version-bump-message: "^(feat)(\\([\\w\\s-]*\\))?:"
patch-version-bump-message: "^(build|chore|ci|docs|fix|perf|refactor|revert|style|test)(\\([\\w\\s-]*\\))?:"
assembly-versioning-scheme: MajorMinorPatch
assembly-file-versioning-scheme: MajorMinorPatch
assembly-informational-format: "{InformationalVersion}"
tag-prefix: "MfGames.Serilog.SpectreExpressions-"

View file

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GitVersion.MsBuild" Version="5.12.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Roslynator.Analyzers" Version="4.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Roslynator.CodeAnalysis.Analyzers" Version="4.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Roslynator.Formatting.Analyzers" Version="4.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Serilog" Version="3.0.1"/>
<PackageReference Include="Serilog.Expressions" Version="3.4.1"/>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0"/>
<PackageReference Include="Spectre.Console" Version="0.47.0"/>
</ItemGroup>
</Project>

View file

@ -1,541 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using Serilog.Core;
using Serilog.Events;
using Serilog.Templates;
using Spectre.Console;
namespace MfGames.ToolBuilder.Services;
public class ExpressionSpectreConsoleSink : ILogEventSink
{
/// <summary>
/// The internal lookup codes for ANSI colors to Spectre color names.
/// https://spectreconsole.net/appendix/colors
/// </summary>
private static readonly Dictionary<string, string> AnsiToSpectre = new()
{
["0"] = "black",
["1"] = "maroon",
["2"] = "green",
["3"] = "olive",
["4"] = "navy",
["5"] = "purple",
["6"] = "teal",
["7"] = "silver",
["8"] = "grey",
["9"] = "red",
["10"] = "lime",
["11"] = "yellow",
["12"] = "blue",
["13"] = "fuchsia",
["14"] = "aqua",
["15"] = "white",
["16"] = "grey0",
["17"] = "navyblue",
["18"] = "darkblue",
["19"] = "blue3",
["20"] = "blue3_1",
["21"] = "blue1",
["22"] = "darkgreen",
["23"] = "deepskyblue4",
["24"] = "deepskyblue4_1",
["25"] = "deepskyblue4_2",
["26"] = "dodgerblue3",
["27"] = "dodgerblue2",
["28"] = "green4",
["29"] = "springgreen4",
["30"] = "turquoise4",
["31"] = "deepskyblue3",
["32"] = "deepskyblue3_1",
["33"] = "dodgerblue1",
["34"] = "green3",
["35"] = "springgreen3",
["36"] = "darkcyan",
["37"] = "lightseagreen",
["38"] = "deepskyblue2",
["39"] = "deepskyblue1",
["40"] = "green3_1",
["41"] = "springgreen3_1",
["42"] = "springgreen2",
["43"] = "cyan3",
["44"] = "darkturquoise",
["45"] = "turquoise2",
["46"] = "green1",
["47"] = "springgreen2_1",
["48"] = "springgreen1",
["49"] = "mediumspringgreen",
["50"] = "cyan2",
["51"] = "cyan1",
["52"] = "darkred",
["53"] = "deeppink4",
["54"] = "purple4",
["55"] = "purple4_1",
["56"] = "purple3",
["57"] = "blueviolet",
["58"] = "orange4",
["59"] = "grey37",
["60"] = "mediumpurple4",
["61"] = "slateblue3",
["62"] = "slateblue3_1",
["63"] = "royalblue1",
["64"] = "chartreuse4",
["65"] = "darkseagreen4",
["66"] = "paleturquoise4",
["67"] = "steelblue",
["68"] = "steelblue3",
["69"] = "cornflowerblue",
["70"] = "chartreuse3",
["71"] = "darkseagreen4_1",
["72"] = "cadetblue",
["73"] = "cadetblue_1",
["74"] = "skyblue3",
["75"] = "steelblue1",
["76"] = "chartreuse3_1",
["77"] = "palegreen3",
["78"] = "seagreen3",
["79"] = "aquamarine3",
["80"] = "mediumturquoise",
["81"] = "steelblue1_1",
["82"] = "chartreuse2",
["83"] = "seagreen2",
["84"] = "seagreen1",
["85"] = "seagreen1_1",
["86"] = "aquamarine1",
["87"] = "darkslategray2",
["88"] = "darkred_1",
["89"] = "deeppink4_1",
["90"] = "darkmagenta",
["91"] = "darkmagenta_1",
["92"] = "darkviolet",
["93"] = "purple_1",
["94"] = "orange4_1",
["95"] = "lightpink4",
["96"] = "plum4",
["97"] = "mediumpurple3",
["98"] = "mediumpurple3_1",
["99"] = "slateblue1",
["100"] = "yellow4",
["101"] = "wheat4",
["102"] = "grey53",
["103"] = "lightslategrey",
["104"] = "mediumpurple",
["105"] = "lightslateblue",
["106"] = "yellow4_1",
["107"] = "darkolivegreen3",
["108"] = "darkseagreen",
["109"] = "lightskyblue3",
["110"] = "lightskyblue3_1",
["111"] = "skyblue2",
["112"] = "chartreuse2_1",
["113"] = "darkolivegreen3_1",
["114"] = "palegreen3_1",
["115"] = "darkseagreen3",
["116"] = "darkslategray3",
["117"] = "skyblue1",
["118"] = "chartreuse1",
["119"] = "lightgreen",
["120"] = "lightgreen_1",
["121"] = "palegreen1",
["122"] = "aquamarine1_1",
["123"] = "darkslategray1",
["124"] = "red3",
["125"] = "deeppink4_2",
["126"] = "mediumvioletred",
["127"] = "magenta3",
["128"] = "darkviolet_1",
["129"] = "purple_2",
["130"] = "darkorange3",
["131"] = "indianred",
["132"] = "hotpink3",
["133"] = "mediumorchid3",
["134"] = "mediumorchid",
["135"] = "mediumpurple2",
["136"] = "darkgoldenrod",
["137"] = "lightsalmon3",
["138"] = "rosybrown",
["139"] = "grey63",
["140"] = "mediumpurple2_1",
["141"] = "mediumpurple1",
["142"] = "gold3",
["143"] = "darkkhaki",
["144"] = "navajowhite3",
["145"] = "grey69",
["146"] = "lightsteelblue3",
["147"] = "lightsteelblue",
["148"] = "yellow3",
["149"] = "darkolivegreen3_2",
["150"] = "darkseagreen3_1",
["151"] = "darkseagreen2",
["152"] = "lightcyan3",
["153"] = "lightskyblue1",
["154"] = "greenyellow",
["155"] = "darkolivegreen2",
["156"] = "palegreen1_1",
["157"] = "darkseagreen2_1",
["158"] = "darkseagreen1",
["159"] = "paleturquoise1",
["160"] = "red3_1",
["161"] = "deeppink3",
["162"] = "deeppink3_1",
["163"] = "magenta3_1",
["164"] = "magenta3_2",
["165"] = "magenta2",
["166"] = "darkorange3_1",
["167"] = "indianred_1",
["168"] = "hotpink3_1",
["169"] = "hotpink2",
["170"] = "orchid",
["171"] = "mediumorchid1",
["172"] = "orange3",
["173"] = "lightsalmon3_1",
["174"] = "lightpink3",
["175"] = "pink3",
["176"] = "plum3",
["177"] = "violet",
["178"] = "gold3_1",
["179"] = "lightgoldenrod3",
["180"] = "tan",
["181"] = "mistyrose3",
["182"] = "thistle3",
["183"] = "plum2",
["184"] = "yellow3_1",
["185"] = "khaki3",
["186"] = "lightgoldenrod2",
["187"] = "lightyellow3",
["188"] = "grey84",
["189"] = "lightsteelblue1",
["190"] = "yellow2",
["191"] = "darkolivegreen1",
["192"] = "darkolivegreen1_1",
["193"] = "darkseagreen1_1",
["194"] = "honeydew2",
["195"] = "lightcyan1",
["196"] = "red1",
["197"] = "deeppink2",
["198"] = "deeppink1",
["199"] = "deeppink1_1",
["200"] = "magenta2_1",
["201"] = "magenta1",
["202"] = "orangered1",
["203"] = "indianred1",
["204"] = "indianred1_1",
["205"] = "hotpink",
["206"] = "hotpink_1",
["207"] = "mediumorchid1_1",
["208"] = "darkorange",
["209"] = "salmon1",
["210"] = "lightcoral",
["211"] = "palevioletred1",
["212"] = "orchid2",
["213"] = "orchid1",
["214"] = "orange1",
["215"] = "sandybrown",
["216"] = "lightsalmon1",
["217"] = "lightpink1",
["218"] = "pink1",
["219"] = "plum1",
["220"] = "gold1",
["221"] = "lightgoldenrod2_1",
["222"] = "lightgoldenrod2_2",
["223"] = "navajowhite1",
["224"] = "mistyrose1",
["225"] = "thistle1",
["226"] = "yellow1",
["227"] = "lightgoldenrod1",
["228"] = "khaki1",
["229"] = "wheat1",
["230"] = "cornsilk1",
["231"] = "grey100",
["232"] = "grey3",
["233"] = "grey7",
["234"] = "grey11",
["235"] = "grey15",
["236"] = "grey19",
["237"] = "grey23",
["238"] = "grey27",
["239"] = "grey30",
["240"] = "grey35",
["241"] = "grey39",
["242"] = "grey42",
["243"] = "grey46",
["244"] = "grey50",
["245"] = "grey54",
["246"] = "grey58",
["247"] = "grey62",
["248"] = "grey66",
["249"] = "grey70",
["250"] = "grey74",
["251"] = "grey78",
["252"] = "grey82",
["253"] = "grey85",
["254"] = "grey89",
["255"] = "grey93",
};
private readonly ExpressionTemplate template;
public ExpressionSpectreConsoleSink(ExpressionTemplate template)
{
this.template = template;
}
public void Emit(LogEvent logEvent)
{
// SpectreConsole cannot handle the ANSI codes that Serilog.Expressions
// generates, but Serilog.Expressions provides no mechanism for swapping
// out the ANSI for the tags that SpectreConsole uses. To get around
// this, we use a hack that parses the expected ANSI codes from
// Expressions and then convert each one into SpectreConsole's tags
// which will then be turned *back* into ANSI codes by that library.
//
// It is frustrating when popular libraries don't give the ability to
// override things like this.
// Start by having Expressions write out the ANSI-formatted code to a
// string so we can loop through it.
StringWriter writer = new();
this.template.Format(logEvent, writer);
string output = writer.ToString();
// Loop through the output and build up a new markup file that uses
// the SpectreConsole codes instead.
StringBuilder markup = new();
bool inTag = false;
for (int i = 0; i < output.Length; i++)
{
// If we aren't an escape character, escape any required fields and
// then add it to the buffer.
char c = output[i];
if (c != '\x1b')
{
// Add in the character, then escape the specials if needed.
markup.Append(output[i]);
if (output[i] is '[' or ']')
{
markup.Append(output[i]);
}
continue;
}
// For escape codes, we want to grab all the escape codes in a
// series so we can do the correct foreground and background color
// codes in a single markup.
List<string> codes = new();
while (output[i] == '\x1b')
{
StringBuilder escape = new();
i += 2;
while (output[i] != 'm')
{
escape.Append(output[i]);
i++;
}
codes.Add(escape.ToString());
}
// Parse the output without the trailing `m` and the escape code.
string spectreMarkup =
this.TranslateAnsiToSpectre(codes, ref inTag);
markup.Append(spectreMarkup);
}
// Send the modified code to SpectreConsole.
markup.Append(Finalize(ref inTag));
AnsiConsole.Markup(markup.ToString());
}
private static string Finalize(ref bool inTag)
{
return GetMarkup("default", "", "", ref inTag);
}
private static string GetColor256(string index)
{
return AnsiToSpectre.TryGetValue(index.TrimStart('0'), out string? name)
? name
: "";
}
private static string GetMarkup(
string foreground,
string background,
string bold,
ref bool inTag)
{
StringBuilder markup = new();
if (inTag)
{
inTag = false;
markup.Append("[/]");
}
if (foreground != "default"
|| background.Length != 0
|| bold.Length != 0)
{
inTag = true;
markup.AppendFormat("[{0}{1}{2}]", bold, foreground, background);
}
return markup.ToString();
}
private string TranslateAnsiToSpectre(List<string> codes, ref bool inTag)
{
// Build up the markup to give by going through the codes and seeing if
// we have to combine everything together.
StringBuilder markup = new();
string bold = "";
string foreground = "default";
string background = "";
foreach (string code in codes)
{
// This will be a semicolon separated list of parameters.
string[] args = code.Split(';');
switch (args[0])
{
case "0": // Reset
markup.Append(
GetMarkup(foreground, background, bold, ref inTag));
break;
case "38": // Foreground Color
switch (args[1])
{
case "5": // 256 color
foreground = GetColor256(args[2]);
break;
}
break;
case "48": // Background color
switch (args[1])
{
case "5": // 256 color
background = " on " + GetColor256(args[2]);
break;
}
break;
default: // Unknown
// If we have more than one, then treat them as individual
// codes.
//
// https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
foreach (string inner in args)
{
switch (inner)
{
case "1": // Bold
bold = "bold ";
break;
case "30":
foreground = "black";
break;
case "31":
foreground = "red";
break;
case "32":
foreground = "green";
break;
case "33":
foreground = "yellow";
break;
case "34":
foreground = "blue";
break;
case "35":
foreground = "magenta";
break;
case "36":
foreground = "cyan";
break;
case "37":
foreground = "white";
break;
case "39":
foreground = "default";
break;
case "40":
background = " on black";
break;
case "41":
background = " on red";
break;
case "42":
background = " on green";
break;
case "43":
background = " on yellow";
break;
case "44":
background = " on blue";
break;
case "45":
background = " on magenta";
break;
case "46":
background = " on cyan";
break;
case "47":
background = " on white";
break;
case "49":
background = " on default";
break;
default:
break;
}
}
break;
}
// Finish up the codes.
markup.Append(GetMarkup(foreground, background, bold, ref inTag));
}
// Return the resulting markup.
return markup.ToString();
}
}

View file

@ -1,46 +0,0 @@
using Serilog;
using Serilog.Configuration;
using Serilog.Core;
using Serilog.Events;
using Serilog.Templates;
namespace MfGames.ToolBuilder.Services;
public static class ExpressionSpectreConsoleSinkExtensions
{
private const string DefaultConsoleOutputTemplate =
"[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}";
/// <summary>
/// Write log events to the console using Spectre.Console.
/// </summary>
/// <param name="loggerConfiguration">Logger sink configuration.</param>
/// <param name="outputTemplate">
/// A message template describing the format used to write to the sink.
/// The default is "[{Timestamp:HH:mm:ss} {Level:u3}]
/// {Message:lj}{NewLine}{Exception}".
/// </param>
/// <param name="restrictedToMinimumLevel">
/// The minimum level for
/// events passed through the sink. Ignored when <paramref name="levelSwitch" /> is
/// specified.
/// </param>
/// <param name="levelSwitch">
/// A switch allowing the pass-through minimum level
/// to be changed at runtime.
/// </param>
/// <returns>Configuration object allowing method chaining.</returns>
public static LoggerConfiguration SpectreExpression(
this LoggerSinkConfiguration loggerConfiguration,
ExpressionTemplate? outputTemplate = null,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
LoggingLevelSwitch? levelSwitch = null)
{
outputTemplate ??= new ExpressionTemplate(DefaultConsoleOutputTemplate);
return loggerConfiguration.Sink(
new ExpressionSpectreConsoleSink(outputTemplate),
restrictedToMinimumLevel,
levelSwitch);
}
}

View file

@ -0,0 +1,19 @@
namespace MfGames.ToolBuilder.Services;
public enum LogContextFormat
{
/// <summary>
/// Indicates that no log context should be shown.
/// </summary>
None,
/// <summary>
/// Indicates that only the class name should be shown.
/// </summary>
Class,
/// <summary>
/// Indicates that the full path should be shown.
/// </summary>
Full,
}

View file

@ -1,76 +0,0 @@
using System;
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.Services;
/// <summary>
/// A service for handling logging options.
/// </summary>
public class LoggingToolGlobal
{
public LoggingToolGlobal()
{
this.LogLevelOption = new Option<string>(
"--log-level",
() => nameof(LogEventLevel.Information),
string.Format(
"Controls the verbosity of the output, not case-sensitive and prefixes allowed: {0}",
string.Join(", ", Enum.GetNames<LogEventLevel>())));
}
/// <summary>
/// Gets the common option for setting the log level.
/// </summary>
public Option<string> LogLevelOption { get; }
/// <summary>
/// Adds the common options to the command.
/// </summary>
/// <param name="root"></param>
public void Attach(Command root)
{
root.AddGlobalOption(this.LogLevelOption);
}
/// <summary>
/// Sets up logging based on the global settings.
/// </summary>
/// <param name="arguments">The arguments to the command.</param>
public void Configure(string[] arguments)
{
// Figure out the logging level.
string level = GlobalOptionHelper.GetArgumentValue(
this.LogLevelOption,
arguments,
"Warning");
LogEventLevel logLevel = level.GetEnumFuzzy<LogEventLevel>(
"log level");
// Figure out the expression template we want to use.
var template = new ExpressionTemplate(
"[{@t:HH:mm:ss} {@l:u3}] {@m}\n{@x}",
theme: TemplateTheme.Literate);
// Figure out how we are going to configure the logger.
var configuration = new LoggerConfiguration();
Logger logger = configuration
.Enrich.WithDemystifiedStackTraces()
.Enrich.WithExceptionDetails()
.MinimumLevel.Is(logLevel)
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.WriteTo.SpectreExpression(template)
.CreateLogger();
Log.Logger = logger;
}
}

View file

@ -0,0 +1,204 @@
using System.CommandLine;
using MfGames.Serilog.SpectreExpressions;
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.Services;
/// <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.SpectreExpression(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;
}
}

View file

@ -64,6 +64,7 @@
<ItemGroup>
<ProjectReference Include="..\MfGames.IO\MfGames.IO.csproj" />
<ProjectReference Include="..\MfGames.Serilog.SpectreExpressions\MfGames.Serilog.SpectreExpressions.csproj" />
</ItemGroup>
</Project>

View file

@ -32,14 +32,14 @@ namespace MfGames.ToolBuilder
private readonly IHostBuilder hostBuilder;
private readonly LoggingToolGlobal logging;
private readonly LoggingToolService logging;
public ToolBoxBuilder(string configName, string[] arguments)
{
// Create our various services.
this.arguments = arguments;
this.config = new ConfigToolGlobal(configName);
this.logging = new LoggingToolGlobal();
this.logging = new LoggingToolService();
// Set up logging first so we can report the loading process. This
// sets up the Serilog.Log.Logger which means we can use that for
@ -94,14 +94,14 @@ namespace MfGames.ToolBuilder
RootCommand rootCommand = container.Resolve<RootCommand>();
this.config.Attach(rootCommand);
this.logging.Attach(rootCommand);
this.logging.AttachGlobal(rootCommand);
// Finish creating the command line builder so we can make the parser.
CommandLineBuilder builder = new CommandLineBuilder(rootCommand)
.UseDefaults()
.UseHost();
// Finish building the parser, wrap it in a tool box, and return it.
// Finish building the parser, wrap it in a tool box, and return it.
Parser parser = builder.Build();
var toolBox = new ToolBox(this.arguments, parser);

View file

@ -15,7 +15,7 @@ namespace MfGames.ToolBuilder
builder
.RegisterAssemblyTypes(this.GetType().Assembly)
.Except<ConfigToolGlobal>()
.Except<LoggingToolGlobal>()
.Except<LoggingToolService>()
.AsSelf()
.AsImplementedInterfaces();
}