diff --git a/src/rgen/Microsoft.Macios.Transformer/Main.cs b/src/rgen/Microsoft.Macios.Transformer/Main.cs index 20a63d8874b..bf2d2469b8c 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Main.cs +++ b/src/rgen/Microsoft.Macios.Transformer/Main.cs @@ -1,8 +1,16 @@ using System.CommandLine; using Microsoft.CodeAnalysis.CSharp; using Microsoft.Macios.Transformer; +using Serilog; +using Serilog.Core; +using Serilog.Events; +using Serilog.Templates; +using static System.Console; public class Program { + static internal readonly LoggingLevelSwitch LogLevelSwitch = new(LogEventLevel.Information); + public static ILogger logger = Log.ForContext(); + public static int Main (string [] args) { // Create options @@ -30,47 +38,73 @@ public static int Main (string [] args) new [] { "--sdk", "-s" }, "Absolute path to the sdk directory" ); - + + var verbosityOption = new Option (["--verbosity", "-v"], + getDefaultValue: () => Verbosity.Normal) { + IsRequired = false, + Arity = ArgumentArity.ZeroOrOne, + Description = "Set the verbosity level" + }; + // Create root command and add options var rootCmd = new RootCommand ("command to convert outdated bindings to be rgen compatible") { - rspOption, destinationOption, forceOption, workingDirectoryOption, sdkPathOption + rspOption, + destinationOption, + forceOption, + workingDirectoryOption, + sdkPathOption, + verbosityOption }; // If no arguments, show help and exit if (args.Length == 0) { - rootCmd.InvokeAsync (new string [] { "--help" }).Wait (); + rootCmd.InvokeAsync (["--help"]).Wait (); return 0; } // Set handler for parsing and executing - rootCmd.SetHandler (async (rspPath, destPath, workingDirectory, sdkPath, force) => { - // Convert local to absolute, expand ~ - rspPath = ToAbsolutePath (rspPath); - workingDirectory = ToAbsolutePath (workingDirectory); - destPath = ToAbsolutePath (destPath); - sdkPath = ToAbsolutePath (sdkPath); - - ValidateRsp (rspPath); - ValidateSdk (sdkPath); - ValidateWorkingDirectory (workingDirectory); - PrepareDestination (destPath, force); - - // Parse the .rsp file with Roslyn's CSharpCommandLineParser - var args = new string [] { $"@{rspPath}" }; - var parseResult = CSharpCommandLineParser.Default.Parse (args, workingDirectory, null); - Console.WriteLine ($"RSP parsed. Errors: {parseResult.Errors.Length}"); - foreach (var resultError in parseResult.Errors) { - Console.WriteLine (resultError); - } - - await Transformer.Execute ( - destinationDirectory: destPath, - rspFile: rspPath, - workingDirectory: workingDirectory, - sdkDirectory: sdkPath - ); - }, - rspOption, destinationOption, workingDirectoryOption, sdkPathOption, forceOption + rootCmd.SetHandler (async (rspPath, destPath, workingDirectory, sdkPath, force, verbosity) => { + WriteLine ($"Microsoft.Macios.Transformer v{typeof (Program).Assembly.GetName ().Version}, (c) Microsoft Corporation. All rights reserved.\n"); + + // Convert local to absolute, expand ~ + rspPath = ToAbsolutePath (rspPath); + workingDirectory = ToAbsolutePath (workingDirectory); + destPath = ToAbsolutePath (destPath); + sdkPath = ToAbsolutePath (sdkPath); + + ValidateRsp (rspPath); + ValidateSdk (sdkPath); + ValidateWorkingDirectory (workingDirectory); + ValidateVerbosity (verbosity); + PrepareDestination (destPath, force); + + // logging options + Log.Logger = new LoggerConfiguration () + .MinimumLevel.ControlledBy (LogLevelSwitch) + .Enrich.WithThreadName () + .Enrich.WithThreadId () + .Enrich.FromLogContext () + .WriteTo.Console (new ExpressionTemplate( + "[{@t:HH:mm:ss} {@l:u3} {Substring(SourceContext, LastIndexOf(SourceContext, '.') + 1)} (Thread: {ThreadId})] {@m}\n")) + .CreateLogger (); + + // Parse the .rsp file with Roslyn's CSharpCommandLineParser + var args = new [] { $"@{rspPath}" }; + var parseResult = CSharpCommandLineParser.Default.Parse (args, workingDirectory, null); + logger.Information ("RSP parsed. Errors: {ParserErrorLength}", parseResult.Errors.Length); + foreach (var resultError in parseResult.Errors) { + logger.Error ("{Error}", resultError); + WriteLine (resultError); + } + + await Transformer.Execute ( + destinationDirectory: destPath, + rspFile: rspPath, + workingDirectory: workingDirectory, + sdkDirectory: sdkPath + ); + }, + rspOption, destinationOption, workingDirectoryOption, sdkPathOption, forceOption, verbosityOption ); // Invoke command @@ -84,6 +118,7 @@ static string ToAbsolutePath (string path) absolutePath = Path.GetFullPath (absolutePath); return absolutePath; } + static void ValidateRsp (string path) { if (string.IsNullOrWhiteSpace (path) || !File.Exists (path)) @@ -105,6 +140,18 @@ static void ValidateSdk (string path) throw new DirectoryNotFoundException ("Working directory does not exist."); } + static void ValidateVerbosity (Verbosity verbosity) + { + LogLevelSwitch.MinimumLevel = verbosity switch { + Verbosity.Quiet => LogEventLevel.Error, + Verbosity.Minimal => LogEventLevel.Error, + Verbosity.Normal => LogEventLevel.Information, + Verbosity.Detailed => LogEventLevel.Verbose, + Verbosity.Diagnostic => LogEventLevel.Verbose, + _ => LogEventLevel.Information, + }; + } + static void PrepareDestination (string path, bool force) { if (Directory.Exists (path)) { diff --git a/src/rgen/Microsoft.Macios.Transformer/Microsoft.Macios.Transformer.csproj b/src/rgen/Microsoft.Macios.Transformer/Microsoft.Macios.Transformer.csproj index 3d0ac820d62..cfc6a82a5f9 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Microsoft.Macios.Transformer.csproj +++ b/src/rgen/Microsoft.Macios.Transformer/Microsoft.Macios.Transformer.csproj @@ -12,6 +12,11 @@ + + + + + diff --git a/src/rgen/Microsoft.Macios.Transformer/Transformer.cs b/src/rgen/Microsoft.Macios.Transformer/Transformer.cs index fa653a299ab..2dd365393be 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Transformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/Transformer.cs @@ -10,6 +10,7 @@ using Microsoft.Macios.Generator.Extensions; using Microsoft.Macios.Transformer.Extensions; using Microsoft.Macios.Transformer.Workers; +using Serilog; namespace Microsoft.Macios.Transformer; @@ -18,6 +19,7 @@ namespace Microsoft.Macios.Transformer; /// to be able to process the different transformations per binding type. /// class Transformer { + readonly static ILogger logger = Log.ForContext(); readonly string destinationDirectory; readonly Compilation compilation; readonly HashSet? namespaceFilter; @@ -54,6 +56,7 @@ internal async Task CreateHub () var configuration = new TopicConfiguration { Mode = ChannelDeliveryMode.AtLeastOnceSync }; foreach (var (topicName, transformer) in transformers) { await hub.CreateAsync (topicName, configuration, transformer, transformer); + logger.Information ("Created new topic '{TopicName}'", topicName); } return hub; @@ -63,10 +66,15 @@ internal async Task CreateHub () { // get the attrs, based on those return the correct topic to use var attrs = symbol.GetAttributeData (); + logger.Debug ("Symbol '{SymbolName}' has [{Attributes}] attributes", symbol.Name, string.Join (", ", attrs.Keys)); + logger.Debug ("Symbol '{SymbolName}' kind is '{SymbolKind}'", symbol.Name, symbol.TypeKind); + if (symbol.TypeKind == TypeKind.Enum) { // simplest case, an error domain - if (attrs.ContainsKey (AttributesNames.ErrorDomainAttribute)) - return nameof (ErrorDomainTransformer); + if (attrs.ContainsKey (AttributesNames.ErrorDomainAttribute)) { + logger.Debug ("Symbol '{SymbolName}' is an error domain", symbol.Name); + return nameof(ErrorDomainTransformer); + } // in this case, we need to check if the enum is a smart enum. // Smart enum: One of the enum members contains a FieldAttribute. Does NOT have to be ALL @@ -74,27 +82,39 @@ internal async Task CreateHub () foreach (var enumField in enumMembers) { var fieldAttrs = enumField.GetAttributeData (); if (fieldAttrs.ContainsKey (AttributesNames.FieldAttribute)) { + logger.Debug ("Symbol '{SymbolName}' is a smart enum", symbol.Name); return nameof (SmartEnumTransformer); } } // we have either a native enum of a regular enum, we will use the copy worker + logger.Debug ("Symbol '{SymbolName}' is a regular enum", symbol.Name); return nameof (CopyTransformer); } if (attrs.ContainsKey (AttributesNames.BaseTypeAttribute)) { // if can be a class or a protocol, check if the protocol attribute is present if (attrs.ContainsKey (AttributesNames.ProtocolAttribute) || - attrs.ContainsKey (AttributesNames.ModelAttribute)) - return nameof (ProtocolTransformer); - if (attrs.ContainsKey (AttributesNames.CategoryAttribute)) - return nameof (CategoryTransformer); + attrs.ContainsKey (AttributesNames.ModelAttribute)) { + logger.Debug ("Symbol '{SymbolName}' is a protocol", symbol.Name); + return nameof(ProtocolTransformer); + } + + if (attrs.ContainsKey (AttributesNames.CategoryAttribute)) { + logger.Debug ("Symbol '{SymbolName}' is a category", symbol.Name); + return nameof(CategoryTransformer); + } + + logger.Debug ("Symbol '{SymbolName}' is a class", symbol.Name); return nameof (ClassTransformer); } - if (attrs.ContainsKey (AttributesNames.StrongDictionaryAttribute)) - return nameof (StrongDictionaryTransformer); + if (attrs.ContainsKey (AttributesNames.StrongDictionaryAttribute)) { + logger.Debug ("Symbol '{SymbolName}' is a strong dictionary", symbol.Name); + return nameof(StrongDictionaryTransformer); + } + logger.Warning ("Symbol '{SymbolName}' could not be matched to a transformer", symbol.Name); return null; } @@ -108,8 +128,8 @@ internal bool Skip (SyntaxTree syntaxTree, ISymbol symbol, [NotNullWhen (false)] if (namespaceFilter is not null && !namespaceFilter.Contains (symbolNamespace)) { // TODO we could do this better by looking at the tree - Console.WriteLine ( - $"Skipping {symbol.Name} because namespace {symbolNamespace} was not included in the transformation"); + logger.Information ("Skipping '{SymbolName}' because namespace it was not included in the transformation", + symbol.Name, symbolNamespace); // filtered out return true; } @@ -140,19 +160,22 @@ internal async Task Execute () .DescendantNodes () .OfType ().ToArray (); - Console.WriteLine ($"Found {declarations.Length} interfaces in {tree.FilePath}"); + logger.Debug ("Found '{Declarations}' interfaces in '{FilePath}'", declarations.Length, tree.FilePath); // loop over the declarations and send them to the hub foreach (var declaration in declarations) { var symbol = model.GetDeclaredSymbol (declaration); if (symbol is null) { // skip the transformation because the symbol is null + logger.Warning ("Could not get the symbol for '{Declaration}'", declaration.Identifier); continue; } - if (Skip (tree, symbol, out var outputDirectory)) + if (Skip (tree, symbol, out var outputDirectory)) { // matched the filter + logger.Information ("Skipping '{SymbolName}' because it was filtered out", symbol.Name); continue; + } // create the destination directory if needed, this is the only location we should be creating directories Directory.CreateDirectory (outputDirectory); @@ -160,6 +183,7 @@ internal async Task Execute () var topicName = SelectTopic (symbol); if (topicName is not null && transformers.TryGetValue (topicName, out var transformer)) { await hub.PublishAsync (topicName, transformer.CreateMessage (tree, symbol)); + logger.Information ("Published '{SymbolName}' to '{TopicName}'", symbol.Name, topicName); } } } @@ -172,7 +196,7 @@ internal async Task Execute () public static Task Execute (string destinationDirectory, string rspFile, string workingDirectory, string sdkDirectory) { - Console.WriteLine ("Executing transformation"); + logger.Information ("Executing transformation"); // the transformation works as follows. We first need to parse the rsp file to create a compilation // to do so we relay on the csharp compiler, there is not much we really need to do. If the parsing // is wrong, we throw an exception. @@ -190,7 +214,9 @@ public static Task Execute (string destinationDirectory, string rspFile, string .WithDocumentationMode (DocumentationMode.None); var references = parseResult.GetReferences (workingDirectory, sdkDirectory); + logger.Information ("References {References}", references.Length); var parsedSource = parseResult.GetSourceFiles (updatedParseOptions); + logger.Information ("Parsed {Files} files", parsedSource.Length); var compilation = CSharpCompilation.Create ( assemblyName: $"{parseResult.CompilationName}-transformer", @@ -199,7 +225,7 @@ public static Task Execute (string destinationDirectory, string rspFile, string options: parseResult.CompilationOptions); var diagnostics = compilation.GetDiagnostics (); - Console.WriteLine ($"Diagnostics length {diagnostics.Length}"); + logger.Debug ("Total diagnostics length {DiagnosticsLength}", diagnostics.Length); // collect all the compilation errors, ignoring the warnings, if any error is found, we throw an exception var errors = diagnostics.Where (d => d.Severity == DiagnosticSeverity.Error).ToArray (); if (errors.Length > 0) { @@ -207,6 +233,7 @@ public static Task Execute (string destinationDirectory, string rspFile, string foreach (var resultError in errors) { sb.AppendLine ($"{resultError}"); } + logger.Error ("Error during workspace compilation: {Error}", sb); throw new Exception ($"Error during workspace compilation: {sb}"); } diff --git a/src/rgen/Microsoft.Macios.Transformer/Verbosity.cs b/src/rgen/Microsoft.Macios.Transformer/Verbosity.cs new file mode 100644 index 00000000000..d453a840bd6 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Transformer/Verbosity.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Macios.Transformer; + +enum Verbosity { + Quiet, + Minimal, + Normal, + Detailed, + Diagnostic +} + diff --git a/src/rgen/Microsoft.Macios.Transformer/Workers/CategoryTransformer.cs b/src/rgen/Microsoft.Macios.Transformer/Workers/CategoryTransformer.cs index 86f1adb02c0..aa15384774c 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Workers/CategoryTransformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/Workers/CategoryTransformer.cs @@ -3,22 +3,27 @@ using Marille; using Microsoft.CodeAnalysis; +using Serilog; namespace Microsoft.Macios.Transformer.Workers; public class CategoryTransformer (string destinationDirectory) : ITransformer<(string Path, string SymbolName)> { + readonly static ILogger logger = Log.ForContext(); public bool UseBackgroundThread { get => true; } public Task ConsumeAsync ((string Path, string SymbolName) message, CancellationToken token = new ()) { - Console.WriteLine ($"CategoryTransformer: Transforming class {message.SymbolName} for path {message.Path} to {destinationDirectory}"); + logger.Information ("Transforming {SymbolName} for path {Path} to {DestinationDirectory}", + message.SymbolName, message.Path, destinationDirectory); return Task.Delay (10); } public Task ConsumeAsync ((string Path, string SymbolName) message, Exception exception, CancellationToken token = new CancellationToken ()) { + logger.Error (exception, "Error transforming {SymbolName} for path {Path} to {DestinationDirectory}:", + message.SymbolName, message.Path, destinationDirectory); return Task.CompletedTask; } diff --git a/src/rgen/Microsoft.Macios.Transformer/Workers/ClassTransformer.cs b/src/rgen/Microsoft.Macios.Transformer/Workers/ClassTransformer.cs index 1cf6367e9da..dac6f488902 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Workers/ClassTransformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/Workers/ClassTransformer.cs @@ -3,21 +3,26 @@ using Marille; using Microsoft.CodeAnalysis; +using Serilog; namespace Microsoft.Macios.Transformer.Workers; public class ClassTransformer (string destinationDirectory) : ITransformer<(string Path, string SymbolName)> { + readonly static ILogger logger = Log.ForContext(); public ValueTask DisposeAsync () => ValueTask.CompletedTask; public Task ConsumeAsync ((string Path, string SymbolName) message, CancellationToken token = new ()) { - Console.WriteLine ($"ClassTransformer: Transforming class {message.SymbolName} for path {message.Path} to {destinationDirectory}"); + logger.Information ("Transforming {SymbolName} for path {Path} to {DestinationDirectory}", + message.SymbolName, message.Path, destinationDirectory); return Task.Delay (10); } public Task ConsumeAsync ((string Path, string SymbolName) message, Exception exception, CancellationToken token = new CancellationToken ()) { + logger.Error (exception, "Error transforming {SymbolName} for path {Path} to {DestinationDirectory}:", + message.SymbolName, message.Path, destinationDirectory); return Task.CompletedTask; } diff --git a/src/rgen/Microsoft.Macios.Transformer/Workers/CopyTransformer.cs b/src/rgen/Microsoft.Macios.Transformer/Workers/CopyTransformer.cs index fcff0f2a384..3f33adf9696 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Workers/CopyTransformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/Workers/CopyTransformer.cs @@ -3,22 +3,27 @@ using Marille; using Microsoft.CodeAnalysis; +using Serilog; namespace Microsoft.Macios.Transformer.Workers; public class CopyTransformer (string destinationDirectory) : ITransformer<(string Path, string SymbolName)> { + readonly static ILogger logger = Log.ForContext(); public bool UseBackgroundThread { get => true; } public Task ConsumeAsync ((string Path, string SymbolName) message, CancellationToken token = new ()) { - Console.WriteLine ($"CopyTransformer: Transforming class {message.SymbolName} for path {message.Path} to {destinationDirectory}"); + logger.Information ("Transforming {SymbolName} for path {Path} to {DestinationDirectory}", + message.SymbolName, message.Path, destinationDirectory); return Task.Delay (10); } public Task ConsumeAsync ((string Path, string SymbolName) message, Exception exception, CancellationToken token = new CancellationToken ()) { + logger.Error (exception, "Error transforming {SymbolName} for path {Path} to {DestinationDirectory}:", + message.SymbolName, message.Path, destinationDirectory); return Task.CompletedTask; } diff --git a/src/rgen/Microsoft.Macios.Transformer/Workers/ErrorDomainTransformer.cs b/src/rgen/Microsoft.Macios.Transformer/Workers/ErrorDomainTransformer.cs index 73c2c579cb9..901e04e2973 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Workers/ErrorDomainTransformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/Workers/ErrorDomainTransformer.cs @@ -3,23 +3,28 @@ using Marille; using Microsoft.CodeAnalysis; +using Serilog; namespace Microsoft.Macios.Transformer.Workers; public class ErrorDomainTransformer (string destinationDirectory) : ITransformer<(string Path, string SymbolName)> { + readonly static ILogger logger = Log.ForContext(); public bool UseBackgroundThread { get => true; } public Task ConsumeAsync ((string Path, string SymbolName) message, CancellationToken token = new ()) { - Console.WriteLine ($"CopyTransformer: Transforming class {message.SymbolName} for path {message.Path} to {destinationDirectory}"); + logger.Information ("Transforming {SymbolName} for path {Path} to {DestinationDirectory}", + message.SymbolName, message.Path, destinationDirectory); return Task.Delay (10); } public Task ConsumeAsync ((string Path, string SymbolName) message, Exception exception, CancellationToken token = new CancellationToken ()) { + logger.Error (exception, "Error transforming {SymbolName} for path {Path} to {DestinationDirectory}:", + message.SymbolName, message.Path, destinationDirectory); return Task.CompletedTask; } diff --git a/src/rgen/Microsoft.Macios.Transformer/Workers/ProtocolTransformer.cs b/src/rgen/Microsoft.Macios.Transformer/Workers/ProtocolTransformer.cs index 7032f8e853b..607c6811e39 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Workers/ProtocolTransformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/Workers/ProtocolTransformer.cs @@ -3,21 +3,26 @@ using Marille; using Microsoft.CodeAnalysis; +using Serilog; namespace Microsoft.Macios.Transformer.Workers; public class ProtocolTransformer (string destinationDirectory) : ITransformer<(string Path, string SymbolName)> { + readonly static ILogger logger = Log.ForContext(); public bool UseBackgroundThread { get => true; } public Task ConsumeAsync ((string Path, string SymbolName) message, CancellationToken token = new ()) { - Console.WriteLine ($"ProtocolTransformer: Transforming class {message.SymbolName} for path {message.Path} to {destinationDirectory}"); + logger.Information ("Transforming {SymbolName} for path {Path} to {DestinationDirectory}", + message.SymbolName, message.Path, destinationDirectory); return Task.Delay (10); } public Task ConsumeAsync ((string Path, string SymbolName) message, Exception exception, CancellationToken token = new CancellationToken ()) { + logger.Error (exception, "Error transforming {SymbolName} for path {Path} to {DestinationDirectory}:", + message.SymbolName, message.Path, destinationDirectory); return Task.CompletedTask; } diff --git a/src/rgen/Microsoft.Macios.Transformer/Workers/SmartEnumTransformer.cs b/src/rgen/Microsoft.Macios.Transformer/Workers/SmartEnumTransformer.cs index f04d83c4131..7ef055f4f40 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Workers/SmartEnumTransformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/Workers/SmartEnumTransformer.cs @@ -3,22 +3,27 @@ using Marille; using Microsoft.CodeAnalysis; +using Serilog; namespace Microsoft.Macios.Transformer.Workers; public class SmartEnumTransformer (string destinationDirectory) : ITransformer<(string Path, string SymbolName)> { + readonly static ILogger logger = Log.ForContext(); public bool UseBackgroundThread { get => true; } public Task ConsumeAsync ((string Path, string SymbolName) message, CancellationToken token = new ()) { - Console.WriteLine ($"SmartEnumTransformer: Transforming class {message.SymbolName} for path {message.Path} to {destinationDirectory}"); + logger.Information ("Transforming {SymbolName} for path {Path} to {DestinationDirectory}", + message.SymbolName, message.Path, destinationDirectory); return Task.Delay (10); } public Task ConsumeAsync ((string Path, string SymbolName) message, Exception exception, CancellationToken token = new CancellationToken ()) { + logger.Error (exception, "Error transforming {SymbolName} for path {Path} to {DestinationDirectory}:", + message.SymbolName, message.Path, destinationDirectory); return Task.CompletedTask; } diff --git a/src/rgen/Microsoft.Macios.Transformer/Workers/StrongDictionaryTransformer.cs b/src/rgen/Microsoft.Macios.Transformer/Workers/StrongDictionaryTransformer.cs index 287ba445a5b..2de977540d2 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Workers/StrongDictionaryTransformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/Workers/StrongDictionaryTransformer.cs @@ -3,21 +3,26 @@ using Marille; using Microsoft.CodeAnalysis; +using Serilog; namespace Microsoft.Macios.Transformer.Workers; public class StrongDictionaryTransformer (string destinationDirectory) : ITransformer<(string Path, string SymbolName)> { + readonly static ILogger logger = Log.ForContext(); public bool UseBackgroundThread { get => true; } public Task ConsumeAsync ((string Path, string SymbolName) message, CancellationToken token = new ()) { - Console.WriteLine ($"StrongDictionaryTransformer: Transforming class {message.SymbolName} for path {message.Path} to {destinationDirectory}"); + logger.Information ("Transforming {SymbolName} for path {Path} to {DestinationDirectory}", + message.SymbolName, message.Path, destinationDirectory); return Task.Delay (10); } public Task ConsumeAsync ((string Path, string SymbolName) message, Exception exception, CancellationToken token = new CancellationToken ()) { + logger.Error (exception, "Error transforming {SymbolName} for path {Path} to {DestinationDirectory}:", + message.SymbolName, message.Path, destinationDirectory); return Task.CompletedTask; }