mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-16 08:52:50 +08:00
Serilog example for logging
This commit is contained in:
parent
29e6e34f83
commit
1cd335e785
35
examples/Cli/Logging/Commands/HelloCommand.cs
Normal file
35
examples/Cli/Logging/Commands/HelloCommand.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Spectre.Console;
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
|
namespace Logging.Commands
|
||||||
|
{
|
||||||
|
public class HelloCommand : Command<HelloCommand.Settings>
|
||||||
|
{
|
||||||
|
private ILogger<HelloCommand> _logger;
|
||||||
|
private IAnsiConsole _console;
|
||||||
|
|
||||||
|
public HelloCommand(IAnsiConsole console, ILogger<HelloCommand> logger)
|
||||||
|
{
|
||||||
|
_console = console;
|
||||||
|
_logger = logger;
|
||||||
|
_logger.LogDebug("{0} initialized", nameof(HelloCommand));
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Settings : LogCommandSettings
|
||||||
|
{
|
||||||
|
[CommandArgument(0, "[Name]")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override int Execute(CommandContext context, Settings settings)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Starting my command");
|
||||||
|
AnsiConsole.MarkupLine($"Hello, [blue]{settings.Name}[/]");
|
||||||
|
_logger.LogInformation("Completed my command");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
examples/Cli/Logging/Commands/LogCommandSettings.cs
Normal file
56
examples/Cli/Logging/Commands/LogCommandSettings.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using Serilog.Events;
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
|
namespace Logging.Commands
|
||||||
|
{
|
||||||
|
public class LogCommandSettings : CommandSettings
|
||||||
|
{
|
||||||
|
[CommandOption("--logFile")]
|
||||||
|
[Description("Path and file name for logging")]
|
||||||
|
public string LogFile { get; set; }
|
||||||
|
|
||||||
|
[CommandOption("--logLevel")]
|
||||||
|
[Description("Minimum level for logging")]
|
||||||
|
[TypeConverter(typeof(VerbosityConverter))]
|
||||||
|
[DefaultValue(LogEventLevel.Information)]
|
||||||
|
public LogEventLevel LogLevel { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class VerbosityConverter : TypeConverter
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, LogEventLevel> _lookup;
|
||||||
|
|
||||||
|
public VerbosityConverter()
|
||||||
|
{
|
||||||
|
_lookup = new Dictionary<string, LogEventLevel>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{"d", LogEventLevel.Debug},
|
||||||
|
{"v", LogEventLevel.Verbose},
|
||||||
|
{"i", LogEventLevel.Information},
|
||||||
|
{"w", LogEventLevel.Warning},
|
||||||
|
{"e", LogEventLevel.Error},
|
||||||
|
{"f", LogEventLevel.Fatal}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||||
|
{
|
||||||
|
if (value is string stringValue)
|
||||||
|
{
|
||||||
|
var result = _lookup.TryGetValue(stringValue, out var verbosity);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
const string format = "The value '{0}' is not a valid verbosity.";
|
||||||
|
var message = string.Format(CultureInfo.InvariantCulture, format, value);
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
return verbosity;
|
||||||
|
}
|
||||||
|
throw new NotSupportedException("Can't convert value to verbosity.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
examples/Cli/Logging/Infrastructure/LogInterceptor.cs
Normal file
20
examples/Cli/Logging/Infrastructure/LogInterceptor.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using Logging.Commands;
|
||||||
|
using Serilog.Core;
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
|
namespace Logging
|
||||||
|
{
|
||||||
|
public class LogInterceptor : ICommandInterceptor
|
||||||
|
{
|
||||||
|
public static readonly LoggingLevelSwitch LogLevel = new();
|
||||||
|
|
||||||
|
public void Intercept(CommandContext context, CommandSettings settings)
|
||||||
|
{
|
||||||
|
if (settings is LogCommandSettings logSettings)
|
||||||
|
{
|
||||||
|
LoggingEnricher.Path = logSettings.LogFile ?? "application.log";
|
||||||
|
LogLevel.MinimumLevel = logSettings.LogLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
examples/Cli/Logging/Infrastructure/LoggingEnricher.cs
Normal file
38
examples/Cli/Logging/Infrastructure/LoggingEnricher.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using Serilog.Core;
|
||||||
|
using Serilog.Events;
|
||||||
|
|
||||||
|
namespace Logging
|
||||||
|
{
|
||||||
|
internal class LoggingEnricher : ILogEventEnricher
|
||||||
|
{
|
||||||
|
private string _cachedLogFilePath;
|
||||||
|
private LogEventProperty _cachedLogFilePathProperty;
|
||||||
|
|
||||||
|
// this path and level will be set by the LogInterceptor.cs after parsing the settings
|
||||||
|
public static string Path = string.Empty;
|
||||||
|
|
||||||
|
public const string LogFilePathPropertyName = "LogFilePath";
|
||||||
|
|
||||||
|
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||||
|
{
|
||||||
|
// the settings might not have a path or we might not be within a command in which case
|
||||||
|
// we won't have the setting so a default value for the log file will be required
|
||||||
|
LogEventProperty logFilePathProperty;
|
||||||
|
|
||||||
|
if (_cachedLogFilePathProperty != null && Path.Equals(_cachedLogFilePath))
|
||||||
|
{
|
||||||
|
// Path hasn't changed, so let's use the cached property
|
||||||
|
logFilePathProperty = _cachedLogFilePathProperty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We've got a new path for the log. Let's create a new property
|
||||||
|
// and cache it for future log events to use
|
||||||
|
_cachedLogFilePath = Path;
|
||||||
|
_cachedLogFilePathProperty = logFilePathProperty = propertyFactory.CreateProperty(LogFilePathPropertyName, Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
logEvent.AddPropertyIfAbsent(logFilePathProperty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
examples/Cli/Logging/Infrastructure/TypeRegistrar.cs
Normal file
41
examples/Cli/Logging/Infrastructure/TypeRegistrar.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
|
namespace Logging
|
||||||
|
{
|
||||||
|
public sealed class TypeRegistrar : ITypeRegistrar
|
||||||
|
{
|
||||||
|
private readonly IServiceCollection _builder;
|
||||||
|
|
||||||
|
public TypeRegistrar(IServiceCollection builder)
|
||||||
|
{
|
||||||
|
_builder = builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITypeResolver Build()
|
||||||
|
{
|
||||||
|
return new TypeResolver(_builder.BuildServiceProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Register(Type service, Type implementation)
|
||||||
|
{
|
||||||
|
_builder.AddSingleton(service, implementation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterInstance(Type service, object implementation)
|
||||||
|
{
|
||||||
|
_builder.AddSingleton(service, implementation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterLazy(Type service, Func<object> func)
|
||||||
|
{
|
||||||
|
if (func is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
_builder.AddSingleton(service, _ => func());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
examples/Cli/Logging/Infrastructure/TypeResolver.cs
Normal file
21
examples/Cli/Logging/Infrastructure/TypeResolver.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
|
namespace Logging
|
||||||
|
{
|
||||||
|
public sealed class TypeResolver : ITypeResolver
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _provider;
|
||||||
|
|
||||||
|
public TypeResolver(IServiceProvider provider)
|
||||||
|
{
|
||||||
|
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Resolve(Type type)
|
||||||
|
{
|
||||||
|
return _provider.GetRequiredService(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
examples/Cli/Logging/Logging.csproj
Normal file
26
examples/Cli/Logging/Logging.csproj
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<ExampleName>Logging</ExampleName>
|
||||||
|
<ExampleDescription>Demonstrates how to dynamically configure Serilog for logging using parameters from a command.</ExampleDescription>
|
||||||
|
<ExampleGroup>Cli</ExampleGroup>
|
||||||
|
<ExampleVisible>false</ExampleVisible>
|
||||||
|
<Nullable>disable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
|
||||||
|
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||||
|
<PackageReference Include="Serilog.Extensions.Logging" Version="3.0.1" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Map" Version="1.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\src\Spectre.Console\Spectre.Console.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
54
examples/Cli/Logging/Program.cs
Normal file
54
examples/Cli/Logging/Program.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
using Logging.Commands;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Serilog;
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dynamically control serilog configuration via command line parameters
|
||||||
|
*
|
||||||
|
* This works around the chicken and egg situation with configuring serilog via the command line.
|
||||||
|
* The logger needs to be configured prior to executing the parser, but the logger needs the parsed values
|
||||||
|
* to be configured. By using serilog.sinks.map we can defer configuration. We use a LogLevelSwitch to control the
|
||||||
|
* logging levels dynamically, and then we use a serilog enricher that has it's state populated via a
|
||||||
|
* Spectre.Console CommandInterceptor
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Logging
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
static int Main(string[] args)
|
||||||
|
{
|
||||||
|
// to retrieve the log file name, we must first parse the command settings
|
||||||
|
// this will require us to delay setting the file path for the file writer.
|
||||||
|
// With serilog we can use an enricher and Serilog.Sinks.Map to dynamically
|
||||||
|
// pull this setting.
|
||||||
|
var serviceCollection = new ServiceCollection()
|
||||||
|
.AddLogging(configure =>
|
||||||
|
configure.AddSerilog(new LoggerConfiguration()
|
||||||
|
// log level will be dynamically be controlled by our log interceptor upon running
|
||||||
|
.MinimumLevel.ControlledBy(LogInterceptor.LogLevel)
|
||||||
|
// the log enricher will add a new property with the log file path from the settings
|
||||||
|
// that we can use to set the path dynamically
|
||||||
|
.Enrich.With<LoggingEnricher>()
|
||||||
|
// serilog.sinks.map will defer the configuration of the sink to be ondemand
|
||||||
|
// allowing us to look at the properties set by the enricher to set the path appropriately
|
||||||
|
.WriteTo.Map(LoggingEnricher.LogFilePathPropertyName,
|
||||||
|
(logFilePath, wt) => wt.File($"{logFilePath}"), 1)
|
||||||
|
.CreateLogger()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
var registrar = new TypeRegistrar(serviceCollection);
|
||||||
|
var app = new CommandApp(registrar);
|
||||||
|
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.SetInterceptor(new LogInterceptor()); // add the interceptor
|
||||||
|
config.AddCommand<HelloCommand>("hello");
|
||||||
|
});
|
||||||
|
|
||||||
|
return app.Run(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -80,6 +80,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{E0E4
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Trees", "..\examples\Console\Trees\Trees.csproj", "{CA7AF967-3FA5-4CB1-9564-740CF4527895}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Trees", "..\examples\Console\Trees\Trees.csproj", "{CA7AF967-3FA5-4CB1-9564-740CF4527895}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logging", "..\examples\Cli\Logging\Logging.csproj", "{33C7075A-DF97-44FC-8AB3-0CCFBA03341A}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -426,6 +428,18 @@ Global
|
|||||||
{CA7AF967-3FA5-4CB1-9564-740CF4527895}.Release|x64.Build.0 = Release|Any CPU
|
{CA7AF967-3FA5-4CB1-9564-740CF4527895}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{CA7AF967-3FA5-4CB1-9564-740CF4527895}.Release|x86.ActiveCfg = Release|Any CPU
|
{CA7AF967-3FA5-4CB1-9564-740CF4527895}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{CA7AF967-3FA5-4CB1-9564-740CF4527895}.Release|x86.Build.0 = Release|Any CPU
|
{CA7AF967-3FA5-4CB1-9564-740CF4527895}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{33C7075A-DF97-44FC-8AB3-0CCFBA03341A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{33C7075A-DF97-44FC-8AB3-0CCFBA03341A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{33C7075A-DF97-44FC-8AB3-0CCFBA03341A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{33C7075A-DF97-44FC-8AB3-0CCFBA03341A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{33C7075A-DF97-44FC-8AB3-0CCFBA03341A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{33C7075A-DF97-44FC-8AB3-0CCFBA03341A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{33C7075A-DF97-44FC-8AB3-0CCFBA03341A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{33C7075A-DF97-44FC-8AB3-0CCFBA03341A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{33C7075A-DF97-44FC-8AB3-0CCFBA03341A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{33C7075A-DF97-44FC-8AB3-0CCFBA03341A}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{33C7075A-DF97-44FC-8AB3-0CCFBA03341A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{33C7075A-DF97-44FC-8AB3-0CCFBA03341A}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -458,6 +472,7 @@ Global
|
|||||||
{E9C02C5A-710C-4A57-A008-E3EAC89305CC} = {42792D7F-0BB6-4EE1-A314-8889305A4C48}
|
{E9C02C5A-710C-4A57-A008-E3EAC89305CC} = {42792D7F-0BB6-4EE1-A314-8889305A4C48}
|
||||||
{F83CB4F1-95B8-45A4-A415-6DB5F8CA1E12} = {42792D7F-0BB6-4EE1-A314-8889305A4C48}
|
{F83CB4F1-95B8-45A4-A415-6DB5F8CA1E12} = {42792D7F-0BB6-4EE1-A314-8889305A4C48}
|
||||||
{CA7AF967-3FA5-4CB1-9564-740CF4527895} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
{CA7AF967-3FA5-4CB1-9564-740CF4527895} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
||||||
|
{33C7075A-DF97-44FC-8AB3-0CCFBA03341A} = {42792D7F-0BB6-4EE1-A314-8889305A4C48}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}
|
SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user