{"id":13629487,"url":"https://github.com/DavidFineboym/LoggingDecoratorGenerator","last_synced_at":"2025-04-17T09:34:10.527Z","repository":{"id":124260485,"uuid":"580108731","full_name":"DavidFineboym/LoggingDecoratorGenerator","owner":"DavidFineboym","description":null,"archived":false,"fork":false,"pushed_at":"2024-01-13T07:49:56.000Z","size":155,"stargazers_count":12,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-08-01T11:10:52.917Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DavidFineboym.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2022-12-19T18:35:55.000Z","updated_at":"2024-03-19T00:31:34.000Z","dependencies_parsed_at":"2023-12-22T09:53:41.284Z","dependency_job_id":null,"html_url":"https://github.com/DavidFineboym/LoggingDecoratorGenerator","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidFineboym%2FLoggingDecoratorGenerator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidFineboym%2FLoggingDecoratorGenerator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidFineboym%2FLoggingDecoratorGenerator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidFineboym%2FLoggingDecoratorGenerator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DavidFineboym","download_url":"https://codeload.github.com/DavidFineboym/LoggingDecoratorGenerator/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223751149,"owners_count":17196580,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T22:01:11.847Z","updated_at":"2024-11-08T20:31:01.737Z","avatar_url":"https://github.com/DavidFineboym.png","language":"C#","readme":"![image](https://github.com/DavidFineboym/LoggingDecoratorGenerator/actions/workflows/dotnet.yml/badge.svg?event=push)\n# Logging Decorator Source Generator\n\nGenerates 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.\n- Logs method parameters and return value(can omit secrets from log using `[NotLoggedAttribute]`)\n- Supports async methods\n- Supports log level, event id, and event name override through attribute\n- Can catch and log specific exceptions\n- Can measure method duration for performance reporting either as metric or log message\n- Follows [High-performance logging in .NET](https://learn.microsoft.com/en-us/dotnet/core/extensions/high-performance-logging) guidance\n\n## Getting started\n\nInstall the package from [NuGet](https://www.nuget.org/packages/Fineboym.Logging.Generator)\n\nUse `[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-\u003eAnalyzers-\u003eFineboym.Logging.Generator.\n\n### Prerequisites\n\nLatest version of Visual Studio 2022.\n\n## Usage\n\n```C#\nusing Fineboym.Logging.Attributes;\nusing Microsoft.Extensions.Logging;\n\nnamespace SomeFolder.SomeSubFolder;\n\n// Default log level is Debug, applied to all methods. Can be changed through attribute's constructor.\n[DecorateWithLogger(ReportDurationAsMetric = false)]\npublic interface ISomeService\n{\n    int SomeMethod(DateTime someDateTime);\n\n    // Override log level and event id. EventName is also supported.\n    [LogMethod(Level = LogLevel.Information, EventId = 100, MeasureDuration = true)]\n    Task\u003cdouble?\u003e SomeAsyncMethod(string? s);\n\n    // By default, exceptions are not logged and there is no try-catch block around the method call.\n    // If you want to log exceptions, use ExceptionToLog property.\n    // Default log level for exceptions is Error and it can be changed through ExceptionLogLevel property.\n    [LogMethod(ExceptionToLog = typeof(InvalidOperationException))]\n    Task\u003cstring\u003e AnotherAsyncMethod(int x);\n\n    // You can omit secrets or PII from logs using [NotLogged] attribute.\n    [return: NotLogged]\n    string GetMySecretString(string username, [NotLogged] string password);\n}\n```\nThis will create a generated class named `SomeServiceLoggingDecorator` in the same namespace as the interface.\n\u003cdetails\u003e\u003csummary\u003eClick to see the generated code\u003c/summary\u003e\n\n```C#\n#nullable enable\n\nnamespace SomeFolder.SomeSubFolder\n{\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Fineboym.Logging.Generator\", \"1.10.0.0\")]\n    public sealed class SomeServiceLoggingDecorator : ISomeService\n    {\n        private readonly global::Microsoft.Extensions.Logging.ILogger\u003cISomeService\u003e _logger;\n        private readonly ISomeService _decorated;\n\n        public SomeServiceLoggingDecorator(\n            global::Microsoft.Extensions.Logging.ILogger\u003cISomeService\u003e logger,\n            ISomeService decorated)\n        {\n            _logger = logger;\n            _decorated = decorated;\n        }\n\n        private static readonly global::System.Action\u003cglobal::Microsoft.Extensions.Logging.ILogger, global::System.DateTime, global::System.Exception?\u003e s_beforeSomeMethod\n            = global::Microsoft.Extensions.Logging.LoggerMessage.Define\u003cglobal::System.DateTime\u003e(\n                global::Microsoft.Extensions.Logging.LogLevel.Debug,\n                new global::Microsoft.Extensions.Logging.EventId(15022964, nameof(SomeMethod)),\n                \"Entering SomeMethod with parameters: someDateTime = {someDateTime}\",\n                new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });\n\n        private static readonly global::System.Action\u003cglobal::Microsoft.Extensions.Logging.ILogger, int, global::System.Exception?\u003e s_afterSomeMethod\n            = global::Microsoft.Extensions.Logging.LoggerMessage.Define\u003cint\u003e(\n                global::Microsoft.Extensions.Logging.LogLevel.Debug,\n                new global::Microsoft.Extensions.Logging.EventId(15022964, nameof(SomeMethod)),\n                \"Method SomeMethod returned. Result = {result}\",\n                new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });\n\n        public int SomeMethod(global::System.DateTime someDateTime)\n        {\n            var __logEnabled = _logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Debug);\n\n            if (__logEnabled)\n            {\n                s_beforeSomeMethod(_logger, someDateTime, null);\n            }\n\n            var __result = _decorated.SomeMethod(someDateTime);\n\n            if (__logEnabled)\n            {\n                s_afterSomeMethod(_logger, __result, null);\n            }\n\n            return __result;\n        }\n\n        private static readonly global::System.Action\u003cglobal::Microsoft.Extensions.Logging.ILogger, string?, global::System.Exception?\u003e s_beforeSomeAsyncMethod\n            = global::Microsoft.Extensions.Logging.LoggerMessage.Define\u003cstring?\u003e(\n                global::Microsoft.Extensions.Logging.LogLevel.Information,\n                new global::Microsoft.Extensions.Logging.EventId(100, nameof(SomeAsyncMethod)),\n                \"Entering SomeAsyncMethod with parameters: s = {s}\",\n                new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });\n\n        private static readonly global::System.Action\u003cglobal::Microsoft.Extensions.Logging.ILogger, double?, double?, global::System.Exception?\u003e s_afterSomeAsyncMethod\n            = global::Microsoft.Extensions.Logging.LoggerMessage.Define\u003cdouble?, double?\u003e(\n                global::Microsoft.Extensions.Logging.LogLevel.Information,\n                new global::Microsoft.Extensions.Logging.EventId(100, nameof(SomeAsyncMethod)),\n                \"Method SomeAsyncMethod returned. Result = {result}. DurationInMilliseconds = {durationInMilliseconds}\",\n                new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });\n\n        public async global::System.Threading.Tasks.Task\u003cdouble?\u003e SomeAsyncMethod(string? s)\n        {\n            var __logEnabled = _logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Information);\n            long __startTimestamp = 0;\n\n            if (__logEnabled)\n            {\n                s_beforeSomeAsyncMethod(_logger, s, null);\n                __startTimestamp = global::System.Diagnostics.Stopwatch.GetTimestamp();\n            }\n\n            var __result = await _decorated.SomeAsyncMethod(s).ConfigureAwait(false);\n\n            if (__logEnabled)\n            {\n                var __elapsedTime = global::System.Diagnostics.Stopwatch.GetElapsedTime(__startTimestamp);\n                s_afterSomeAsyncMethod(_logger, __result, __elapsedTime.TotalMilliseconds, null);\n            }\n\n            return __result;\n        }\n\n        private static readonly global::System.Action\u003cglobal::Microsoft.Extensions.Logging.ILogger, int, global::System.Exception?\u003e s_beforeAnotherAsyncMethod\n            = global::Microsoft.Extensions.Logging.LoggerMessage.Define\u003cint\u003e(\n                global::Microsoft.Extensions.Logging.LogLevel.Debug,\n                new global::Microsoft.Extensions.Logging.EventId(2017861863, nameof(AnotherAsyncMethod)),\n                \"Entering AnotherAsyncMethod with parameters: x = {x}\",\n                new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });\n\n        private static readonly global::System.Action\u003cglobal::Microsoft.Extensions.Logging.ILogger, string, global::System.Exception?\u003e s_afterAnotherAsyncMethod\n            = global::Microsoft.Extensions.Logging.LoggerMessage.Define\u003cstring\u003e(\n                global::Microsoft.Extensions.Logging.LogLevel.Debug,\n                new global::Microsoft.Extensions.Logging.EventId(2017861863, nameof(AnotherAsyncMethod)),\n                \"Method AnotherAsyncMethod returned. Result = {result}\",\n                new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });\n\n        public async global::System.Threading.Tasks.Task\u003cstring\u003e AnotherAsyncMethod(int x)\n        {\n            var __logEnabled = _logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Debug);\n\n            if (__logEnabled)\n            {\n                s_beforeAnotherAsyncMethod(_logger, x, null);\n            }\n\n            string __result;\n            try\n            {\n                __result = await _decorated.AnotherAsyncMethod(x).ConfigureAwait(false);\n            }\n            catch (global::System.InvalidOperationException __e)\n            {\n                global::Microsoft.Extensions.Logging.LoggerExtensions.Log(\n                    _logger,\n                    global::Microsoft.Extensions.Logging.LogLevel.Error,\n                    new global::Microsoft.Extensions.Logging.EventId(2017861863, nameof(AnotherAsyncMethod)),\n                    __e,\n                    \"AnotherAsyncMethod failed\");\n\n                throw;\n            }\n\n            if (__logEnabled)\n            {\n                s_afterAnotherAsyncMethod(_logger, __result, null);\n            }\n\n            return __result;\n        }\n\n        private static readonly global::System.Action\u003cglobal::Microsoft.Extensions.Logging.ILogger, string, global::System.Exception?\u003e s_beforeGetMySecretString\n            = global::Microsoft.Extensions.Logging.LoggerMessage.Define\u003cstring\u003e(\n                global::Microsoft.Extensions.Logging.LogLevel.Debug,\n                new global::Microsoft.Extensions.Logging.EventId(1921103492, nameof(GetMySecretString)),\n                \"Entering GetMySecretString with parameters: username = {username}, password = [REDACTED]\",\n                new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });\n\n        private static readonly global::System.Action\u003cglobal::Microsoft.Extensions.Logging.ILogger, global::System.Exception?\u003e s_afterGetMySecretString\n            = global::Microsoft.Extensions.Logging.LoggerMessage.Define(\n                global::Microsoft.Extensions.Logging.LogLevel.Debug,\n                new global::Microsoft.Extensions.Logging.EventId(1921103492, nameof(GetMySecretString)),\n                \"Method GetMySecretString returned. Result = [REDACTED]\",\n                new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });\n\n        public string GetMySecretString(string username, string password)\n        {\n            var __logEnabled = _logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Debug);\n\n            if (__logEnabled)\n            {\n                s_beforeGetMySecretString(_logger, username, null);\n            }\n\n            var __result = _decorated.GetMySecretString(username, password);\n\n            if (__logEnabled)\n            {\n                s_afterGetMySecretString(_logger, null);\n            }\n\n            return __result;\n        }\n    }\n}\n\n```\n\n\u003c/details\u003e\n\n#### Duration as metric\nReporting duration of methods as a metric has an advantage of being separated from logs, so you can enable one without the other.\nFor example, metrics can be collected ad-hoc by [dotnet-counters](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/metrics-collection#view-metrics-with-dotnet-counters) tool or Prometheus.\u003cbr\u003e\nOnly if `ReportDurationAsMetric` is `true`, then [IMeterFactory](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.metrics.imeterfactory) is required in the decorator class constructor.\nFor the example above, name of the meter will be `decorated.GetType().ToString()` where `ISomeService decorated` is constructor parameter to `SomeServiceLoggingDecorator`.\nName of the instrument is always `\"logging_decorator.method.duration\"` and type is [Histogram\\\u003cdouble\\\u003e](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.metrics.histogram-1).\u003cbr\u003e\nFor more info, see [ASP.NET Core metrics](https://learn.microsoft.com/en-us/aspnet/core/log-mon/metrics/metrics), [.NET observability with OpenTelemetry](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/observability-with-otel).\n\n## Additional documentation\n\nIf you use .NET dependency injection, then you can decorate your service interface. You can do it yourself or use [Scrutor](https://github.com/khellang/Scrutor).\nHere is an explanation [Adding decorated classes to the ASP.NET Core DI container using Scrutor](https://andrewlock.net/adding-decorated-classes-to-the-asp.net-core-di-container-using-scrutor).\u003cbr\u003e\nIf you're not familiar with Source Generators, read [Source Generators](https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview).\n\n## Limitations\n\nCurrently it supports non-generic interfaces, only with methods as its members and up to 6 parameters in a method which is what \n[LoggerMessage.Define Method](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggermessage.define?view=dotnet-plat-ext-7.0) \nsupports. To work around 6 parameters limitation, you can encapsulate some\nparameters in a class or a struct or omit them from logging using `[NotLogged]` attribute.\n\n## Feedback\n\nFeel free to open issues here for questions, bugs, and improvements and I'll try to address them as soon as I can. Thank you.","funding_links":[],"categories":["Contributors Welcome for those","Source Generators"],"sub_categories":["1. [ThisAssembly](https://ignatandrei.github.io/RSCG_Examples/v2/docs/ThisAssembly) , in the [EnhancementProject](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enhancementproject) category","Other"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDavidFineboym%2FLoggingDecoratorGenerator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FDavidFineboym%2FLoggingDecoratorGenerator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDavidFineboym%2FLoggingDecoratorGenerator/lists"}