mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-14 16:02: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
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Trees", "..\examples\Console\Trees\Trees.csproj", "{CA7AF967-3FA5-4CB1-9564-740CF4527895}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logging", "..\examples\Cli\Logging\Logging.csproj", "{33C7075A-DF97-44FC-8AB3-0CCFBA03341A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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|x86.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -458,6 +472,7 @@ Global
|
||||
{E9C02C5A-710C-4A57-A008-E3EAC89305CC} = {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}
|
||||
{33C7075A-DF97-44FC-8AB3-0CCFBA03341A} = {42792D7F-0BB6-4EE1-A314-8889305A4C48}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}
|
||||
|
Loading…
x
Reference in New Issue
Block a user