Command Logger#
TLDR, How to enable#
- Enable the feature with
appRunner.UseCommandLogger()
Command logging#
This feature logs information about the command just before the command method is executed.
The default behavior registers a [cmdlog]
directive that outputs to the console using CommandContext.Console.Out
.
Default Behavior#
Given a program with default configuration
public class Program
{
static int Main(string[] args) => AppRunner.Run(args);
public static AppRunner AppRunner =>
new AppRunner<Program>().UseCommandLogger();
public void Add(IConsole console, int x, int y) => console.WriteLine(x + y);
}
When the [cmdlog]
directive is specified, the command logger middleware will output
- original input from the command line
- target command
- parsed arguments with source of value
- some basic system information
$ example.exe [cmdlog] Add 1 1
***************************************
Original input:
[cmdlog] Add 1 1
command: Add
arguments:
x <Number>
value: 1
inputs: 1
default:
y <Number>
value: 1
inputs: 1
default:
Tool version = doc-examples.dll 1.1.1.1
.Net version = .NET 5.0.13
OS version = Microsoft Windows 10.0.12345
***************************************
2
Configuration#
Here are the available parameters for configuration:
/// <summary>Enable the command logger middleware</summary>
/// <param name="appRunner">The <see cref="AppRunner"/></param>
/// <param name="writerFactory">
/// Provide an action to capture the command logger output.
/// When the action is null,
/// the command will not be logged
/// When the parameter is null,
/// the `[cmdlog]` directive is enabled with Console.Out as the target
/// </param>
/// <param name="excludeSystemInfo">Exclude OS, .net version and tool version</param>
/// <param name="includeAppConfig">Prints the entire app configuration</param>
/// <param name="includeMachineAndUser">Include machine name, username</param>
/// <param name="additionalInfoCallback">Additional information to include.</param>
public static AppRunner UseCommandLogger(this AppRunner appRunner,
Func<CommandContext, Action<string?>?>? writerFactory = null,
bool excludeSystemInfo = false,
bool includeAppConfig = false,
bool includeMachineAndUser = false,
Func<CommandContext, IEnumerable<(string key, string value)>?>? additionalInfoCallback = null)
Include Machine and User#
.UseCommandLogger(
excludeSystemInfo: true,
includeMachineAndUser: true);
$ example.exe [cmdlog] Add 1 1
***************************************
Original input:
[cmdlog] Add 1 1
command: Add
arguments:
x <Number>
value: 1
inputs: 1
default:
y <Number>
value: 1
inputs: 1
default:
Machine = my-machine
Username = \my-machine\username
***************************************
2
Additional information can be provided by setting the additionalInfoCallback
parameter with a
Func<CommandContext, IEnumerable<(string key, string value)>>
. Any CommandContext state can be included.
Include AppConfig#
.UseCommandLogger(includeAppConfig: true);
$ example.exe [cmdlog] Add 1 1
***************************************
Original input:
[cmdlog] Add 1 1
command: Add
arguments:
x <Number>
value: 1
inputs: 1
default:
y <Number>
value: 1
inputs: 1
default:
Tool version = doc-examples.dll 1.1.1.1
.Net version = .NET 5.0.13
OS version = Microsoft Windows 10.0.12345
AppConfig:
AppSettings:
Arguments: ArgumentAppSettings:
BooleanMode: Implicit
DefaultArgumentMode: Operand
DefaultOptionSplit:
DefaultPipeTargetSymbol: ^
SkipArityValidation: False
ArgumentTypeDescriptors: ArgumentTypeDescriptors:
ErrorReportingDescriptor > CommandDotNet.TypeDescriptors.BoolTypeDescriptor
ErrorReportingDescriptor > CommandDotNet.TypeDescriptors.EnumTypeDescriptor
ErrorReportingDescriptor > DelegatedTypeDescriptor<String>: 'Text'
ErrorReportingDescriptor > DelegatedTypeDescriptor<Password>: 'Text'
ErrorReportingDescriptor > DelegatedTypeDescriptor<Char>: 'Character'
ErrorReportingDescriptor > DelegatedTypeDescriptor<Int64>: 'Number'
ErrorReportingDescriptor > DelegatedTypeDescriptor<Int32>: 'Number'
ErrorReportingDescriptor > DelegatedTypeDescriptor<Int16>: 'Number'
ErrorReportingDescriptor > DelegatedTypeDescriptor<UInt64>: 'Number'
ErrorReportingDescriptor > DelegatedTypeDescriptor<UInt32>: 'Number'
ErrorReportingDescriptor > DelegatedTypeDescriptor<UInt16>: 'Number'
ErrorReportingDescriptor > DelegatedTypeDescriptor<Decimal>: 'Decimal'
ErrorReportingDescriptor > DelegatedTypeDescriptor<Double>: 'Decimal'
ErrorReportingDescriptor > DelegatedTypeDescriptor<Single>: 'Decimal'
ErrorReportingDescriptor > CommandDotNet.TypeDescriptors.ComponentModelTypeDescriptor
ErrorReportingDescriptor > CommandDotNet.TypeDescriptors.StringCtorTypeDescriptor
DisableDirectives: False
Help: AppHelpSettings:
ExpandArgumentsInUsage: True
PrintHelpOption: False
TextStyle: Detailed
UsageAppName: example.exe
UsageAppNameStyle: Adaptive
Localize:
Parser: ParseAppSettings:
AllowBackslashOptionPrefix: False
AllowSingleHyphenForLongNames: False
DefaultArgumentSeparatorStrategy: EndOfOptions
IgnoreUnexpectedOperands: False
DependencyResolver:
HelpProvider: CommandDotNet.Help.HelpTextProvider
TokenTransformations:
MiddlewarePipeline:
<>c__DisplayClass4_1.<RunInMem>g__CaptureCommandContext|4
AppRunner.OnRunCompleted
HelpMiddleware.PrintHelp
TokenizerPipeline.TokenizeInputMiddleware
ClassModelingMiddleware.CreateRootCommand
CommandParser.ParseInputMiddleware
ClassModelingMiddleware.AssembleInvocationPipelineMiddleware
HelpMiddleware.CheckIfShouldShowHelp
PipedInputMiddleware.InjectPipedInputToOperandList
BindValuesMiddleware.BindValues
ResolveCommandClassesMiddleware.ResolveCommandClassInstances
ValidateArityMiddleware.ValidateArity
CommandLoggerMiddleware.CommandLogger
ClassModelingMiddleware.InvokeInvocationPipelineMiddleware
ParameterResolvers:
CommandDotNet.CommandContext
CommandDotNet.IConsole
CommandDotNet.IEnvironment
System.Threading.CancellationToken
***************************************
2
Recipes for writerFactory#
Use writerFactory
parameter to conditionally provide a target for the log.
If the factory returns null, the command will not be logged.
How to use:
Change the target output#
writerFactory: ctx => Log.Info
Logs every command to logging framework. See Enable via directive for an example of how to selectively log commands.
Note
This disables [cmdlog]
directive enabled. See Enable via directive for an example of how to enable it again.
Enable via attribute#
public class Program
{
static int Main(string[] args) => AppRunner.Run(args);
public static AppRunner AppRunner => new AppRunner<Program>()
.UseCommandLogger(ctx =>
ctx.ParseResult!.TargetCommand.HasAttribute<LogCommandAttribute>()
? ctx.Console.Out.Write
: null);
[LogCommand]
public void Add(IConsole console, int x, int y) => console.WriteLine(x + y);
public void Subtract(IConsole console, int x, int y) => console.WriteLine(x - y);
}
public class LogCommandAttribute : Attribute { }
Notice Add will always log for the command and Subtract never will.
This can be useful when you have some commands you always want logged.
$ example.exe Add 1 1
***************************************
Original input:
Add 1 1
command: Add
arguments:
x <Number>
value: 1
inputs: 1
default:
y <Number>
value: 1
inputs: 1
default:
Tool version = doc-examples.dll 1.1.1.1
.Net version = .NET 5.0.13
OS version = Microsoft Windows 10.0.12345
***************************************
2
$ example.exe Subtract 1 1
0
Enable via directive#
Allow user to enable as a directive
return ctx.Tokens.TryGetDirective("cmdlog", out _)
? s => ctx.Console.Out.Write(s)
: null;
Note
this is the default behavior.
Enable via root option#
Add an intercepor method to your root command with a --logcmd
option. This also makes the option visible to users via help.
public class Program
{
static int Main(string[] args) => AppRunner.Run(args);
public static AppRunner AppRunner => new AppRunner<Program>();
public Task<int> Interceptor(InterceptorExecutionDelegate next, CommandContext ctx,
[Option(
Description = "Output the command with arguments and system info",
BooleanMode = BooleanMode.Implicit)] bool logcmd)
{
if (logcmd)
{
CommandLogger.Log(ctx);
}
return next();
}
public void Add(IConsole console, int x, int y) => console.WriteLine(x + y);
public void Subtract(IConsole console, int x, int y) => console.WriteLine(x - y);
}
$ example.exe --logcmd Add 1 1
***************************************
Original input:
--logcmd Add 1 1
command: Add
arguments:
x <Number>
value: 1
inputs: 1
default:
y <Number>
value: 1
inputs: 1
default:
options:
logcmd <flag>
value: True
inputs: true (from: --logcmd)
default:
Tool version = doc-examples.dll 1.1.1.1
.Net version = .NET 5.0.13
OS version = Microsoft Windows 10.0.12345
***************************************
2