Source generates logger decorator class for an interface. Uses Microsoft.Extensions.Logging.ILogger to log and requires it in decorator class constructor.
$ dotnet add package Fineboym.Logging.GeneratorGenerates logger decorator class for an interface at compile time(no runtime reflection). Uses Microsoft.Extensions.Logging.ILogger to log and requires it in decorator class constructor.
[NotLoggedAttribute])Use [DecorateWithLogger] attribute in Fineboym.Logging.Attributes namespace on an interface. In Visual Studio you can see the generated code in Solution Explorer if you expand Dependencies->Analyzers->Fineboym.Logging.Generator.
Latest version of Visual Studio 2022.
using Fineboym.Logging.Attributes;
using Microsoft.Extensions.Logging;
namespace SomeFolder.SomeSubFolder;
// Default log level is Debug, applied to all methods. Can be changed through attribute's constructor.
[DecorateWithLogger(ReportDurationAsMetric = false)]
public interface ISomeService
{
int SomeMethod(DateTime someDateTime);
// Override log level and event id. EventName is also supported.
[LogMethod(Level = LogLevel.Information, EventId = 100, MeasureDuration = true)]
Task<double?> SomeAsyncMethod(string? s);
// By default, exceptions are not logged and there is no try-catch block around the method call.
// If you want to log exceptions, use ExceptionToLog property.
// Default log level for exceptions is Error and it can be changed through ExceptionLogLevel property.
[LogMethod(ExceptionToLog = typeof(InvalidOperationException))]
Task<string> AnotherAsyncMethod(int x);
// You can omit secrets or PII from logs using [NotLogged] attribute.
[return: NotLogged]
string GetMySecretString(string username, [NotLogged] string password);
}
This will create a generated class named SomeServiceLoggingDecorator in the same namespace as the interface.
You can see the generated code example for above interface on GitHub README.
Reporting duration of methods as a metric has an advantage of being separated from logs, so you can enable one without the other.
For example, metrics can be collected ad-hoc by dotnet-counters tool or Prometheus.
Only if ReportDurationAsMetric is true, then IMeterFactory is required in the decorator class constructor.
For the example above, name of the meter will be decorated.GetType().ToString() where ISomeService decorated is constructor parameter to SomeServiceLoggingDecorator.
Name of the instrument is always "logging_decorator.method.duration" and type is Histogram<double>.
For more info, see ASP.NET Core metrics, .NET observability with OpenTelemetry.
If you use .NET dependency injection, then you can decorate your service interface. You can do it yourself or use Scrutor. Here is an explanation Adding decorated classes to the ASP.NET Core DI container using Scrutor. If you're not familiar with Source Generators, read Source Generators.
Currently it supports non-generic interfaces, only with methods as its members and up to 6 parameters in a method which is what
LoggerMessage.Define Method
supports. To work around 6 parameters limitation, you can encapsulate some
parameters in a class or a struct or omit them from logging using [NotLogged] attribute.
Please go to GitHub repository for feedback. Feel free to open issues for questions, bugs, and improvements and I'll try to address them as soon as I can. Thank you.