AOP.Logging.SourceGenerator by Dietmar Borgards
NuGet / site data
Details
Info
Name: AOP.Logging.SourceGenerator
Source generator for AOP.Logging framework that provides compile-time method interception and automatic logging code generation.
Author: Dietmar Borgards
NuGet: https://www.nuget.org/packages/AOP.Logging.SourceGenerator/
You can find more details at https://github.com/dborgards/aop.logging.net
Author
Dietmar Borgards

Original Readme
AOP.Logging.NET
A powerful, attribute-based Aspect-Oriented Programming (AOP) logging framework for C# that provides seamless method interception and automatic logging using Source Generators.
Features
- Attribute-Based Logging: Simple, declarative logging with
[LogClass],[LogMethod], and related attributes - Compile-Time Source Generation: Zero runtime overhead with C# Source Generators
- Microsoft.Extensions.Logging Integration: Works with your existing logging infrastructure
- Async/Await Support: Full support for async methods and Task-based operations
- Sensitive Data Protection: Automatically mask sensitive data with
[SensitiveData]attribute - Structured Logging: Rich, contextual logging with proper parameter serialization
- Configurable: Fine-grained control over what gets logged and how
- Dependency Injection: First-class support for Microsoft.Extensions.DependencyInjection
- Performance Optimized: Minimal overhead with intelligent logging decisions
Installation
Install the NuGet packages:
### Core library with attributes
dotnet add package AOP.Logging.Core
### Source Generator (required for compile-time code generation)
dotnet add package AOP.Logging.SourceGenerator
### Dependency Injection extensions
dotnet add package AOP.Logging.DependencyInjection
Quick Start
######### 1. Configure Services
using AOP.Logging.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
// Add AOP logging
services.AddAopLogging(options =>
{
options.DefaultLogLevel = LogLevel.Information;
options.LogExecutionTime = true;
options.LogParameters = true;
options.LogReturnValues = true;
});
// Register your services with logging
services.AddTransientWithLogging<IMyService, MyService>();
})
.Build();
await host.RunAsync();
######### 2. Add Logging to Your Classes
AOP.Logging supports two patterns for adding logging to your methods:
########## Pattern 1: Core Suffix (Traditional - Backward Compatible)
using AOP.Logging.Core.Attributes;
[LogClass]
public partial class MyService : IMyService
{
// Implement your business logic in private *Core methods
private int AddCore(int a, int b)
{
return a + b;
}
[LogMethod(LogLevel.Debug)]
private async Task<string> GetDataAsyncCore(int id)
{
await Task.Delay(100);
return $"Data for {id}";
}
// The Source Generator automatically creates public wrapper methods:
// - public int Add(int a, int b) - with automatic logging
// - public async Task<string> GetDataAsync(int id) - with automatic logging
}
########## Pattern 2: No Core Suffix (New - Flexible)
using AOP.Logging.Core.Attributes;
[LogClass]
public partial class OrderService : IOrderService
{
// Methods without "Core" suffix work too!
private async Task<string> CreateOrder(string customerId, decimal amount)
{
await Task.Delay(50);
return Guid.NewGuid().ToString();
}
// The Source Generator creates wrapper methods with "Logged" suffix:
// - public async Task<string> CreateOrderLogged(string customerId, decimal amount)
}
Important:
- Classes using AOP logging must be declared as
partial. - Core Suffix Pattern: Methods ending with
Coregenerate wrappers without the suffix (e.g.,AddCore→Add) - No Core Pattern: Methods without
Coregenerate wrappers withLoggedsuffix (e.g.,CreateOrder→CreateOrderLogged) - Both patterns can be mixed in the same class for maximum flexibility!
######### 3. Run and See the Logs
info: MyService[0]
Entering MyService.Add
info: MyService[0]
Exiting MyService.Add (took 0ms)
Usage Examples
Note: The examples below show the business logic implementation. You can use either pattern:
- Core Suffix Pattern: Implement methods as
private *Coremethods (e.g.,AddCore,ProcessDataCore), and the generator creates wrappers without the suffix.- No Core Pattern: Implement methods without the Core suffix, and the generator creates wrappers with
Loggedsuffix (e.g.,CreateOrder→CreateOrderLogged).
######### Basic Method Logging
[LogClass]
public partial class CalculatorService
{
private int AddCore(int a, int b) => a + b;
private int MultiplyCore(int a, int b) => a * b;
// Source Generator creates: public int Add(int a, int b) and public int Multiply(int a, int b)
}
######### Custom Log Levels
[LogClass(LogLevel.Debug)]
public partial class DebugService
{
[LogMethod(LogLevel.Warning)]
public void PerformCriticalOperation()
{
// This method will log at Warning level
}
}
######### Sensitive Data Protection
[LogClass]
public partial class UserService
{
public async Task<User> CreateUserAsync(
string email,
[SensitiveData] string password)
{
// password will appear as "***SENSITIVE***" in logs
var user = new User \{ Email = email };
return user;
}
}
######### Exception Logging
[LogClass]
public partial class DataService
{
[LogException(LogLevel.Error)]
public void ProcessData(string data)
{
if (string.IsNullOrEmpty(data))
{
throw new ArgumentException("Data cannot be null");
}
// Exception will be automatically logged
}
}
######### Async Method Support
[LogClass]
public partial class ApiService
{
public async Task<ApiResponse> FetchDataAsync(string endpoint)
{
await Task.Delay(100);
return new ApiResponse \{ Data = "Success" };
}
public async Task<T> GetAsync<T>(string url)
{
// Generic async methods are fully supported
await Task.Delay(50);
return default(T)!;
}
}
######### Parameter Control
[LogClass]
public partial class ReportService
{
public Report Generate(
[LogParameter(Name = "ReportId")] int id,
[LogParameter(Skip = true)] string internalToken,
[LogParameter(MaxLength = 50)] string description)
{
// internalToken won't be logged
// description will be truncated to 50 characters
return new Report \{ Id = id };
}
}
######### Return Value Logging
[LogClass]
public partial class CalculationService
{
[LogResult(Name = "CalculationResult")]
public double Calculate(double x, double y)
{
return Math.Sqrt(x * x + y * y);
}
[LogResult(Skip = true)]
public byte[] GetBinaryData()
{
// Return value won't be logged (useful for large data)
return new byte[1024];
}
}
######### Selective Method Logging
[LogClass]
public partial class MixedService
{
public void LoggedMethod()
{
// This will be logged
}
[LogMethod(Skip = true)]
public void NotLoggedMethod()
{
// This will NOT be logged
}
}
######### Mixed Pattern Usage (Core + No Core)
[LogClass]
public partial class MixedPatternService
{
// Old pattern: Method with "Core" suffix
// Wrapper: public async Task<int> ProcessData(string data)
private async Task<int> ProcessDataCore(string data)
{
await Task.Delay(50);
return data.Length;
}
// New pattern: Method without "Core" suffix
// Wrapper: public bool ValidateInputLogged(string input)
private bool ValidateInput(string input)
{
return !string.IsNullOrEmpty(input);
}
}
######### Selective Logging Without [LogClass]
public partial class SelectiveService
{
// Only this method will be logged (has [LogMethod])
// Wrapper: public async Task<bool> ImportantOperationLogged(string data)
[LogMethod(LogLevel.Information)]
private async Task<bool> ImportantOperation(string data)
{
await Task.Delay(100);
return !string.IsNullOrEmpty(data);
}
// This won't be logged (no [LogMethod] and no [LogClass])
private void UnloggedHelper(string data)
{
// Not logged
}
}
Configuration
######### Global Options
services.AddAopLogging(options =>
{
// Default log level
options.DefaultLogLevel = LogLevel.Information;
// Execution time tracking
options.LogExecutionTime = true;
// Parameter and return value logging
options.LogParameters = true;
options.LogReturnValues = true;
// Exception logging
options.LogExceptions = true;
// String and collection limits
options.MaxStringLength = 1000;
options.MaxCollectionSize = 10;
// Structured logging
options.UseStructuredLogging = true;
// Namespace filtering
options.IncludedNamespaces.Add("MyApp.Services");
options.ExcludedNamespaces.Add("MyApp.Internal");
// Class filtering with wildcards
options.IncludedClasses.Add("*Service");
options.ExcludedClasses.Add("*Internal");
// Custom message formats
options.EntryMessageFormat = "→ {ClassName}.{MethodName}";
options.ExitMessageFormat = "← {ClassName}.{MethodName} ({ExecutionTime}ms)";
options.ExceptionMessageFormat = "✗ {ClassName}.{MethodName}: {ExceptionMessage}";
});
######### Message Format Placeholders
Entry Messages:
{ClassName}- The name of the class{MethodName}- The name of the method{Parameters}- Formatted parameter list
Exit Messages:
{ClassName}- The name of the class{MethodName}- The name of the method{ReturnValue}- The return value{ExecutionTime}- Execution time in milliseconds
Exception Messages:
{ClassName}- The name of the class{MethodName}- The name of the method{ExceptionType}- The exception type name{ExceptionMessage}- The exception message{ExecutionTime}- Execution time before exception
Attributes Reference
######### [LogClass]
Marks a class for automatic logging of all public methods.
[LogClass(LogLevel.Information)]
public partial class MyService \{ }
Properties:
LogLevel- Log level for all methods (default: Information)LogExecutionTime- Track execution time (default: true)LogParameters- Log method parameters (default: true)LogReturnValue- Log return values (default: true)LogExceptions- Log exceptions (default: true)
######### [LogMethod]
Controls logging for a specific method, overriding class-level settings.
[LogMethod(LogLevel.Debug)]
public void MyMethod() \{ }
Properties:
LogLevel- Log level for this methodLogExecutionTime- Track execution timeLogParameters- Log method parametersLogReturnValue- Log return valueLogExceptions- Log exceptionsSkip- Skip logging for this methodEntryMessage- Custom entry message templateExitMessage- Custom exit message template
######### [LogParameter]
Controls logging for a specific parameter.
public void MyMethod([LogParameter(Name = "UserId")] int id) \{ }
Properties:
Skip- Skip logging this parameterName- Custom name in logsMaxLength- Maximum length for string values
######### [LogResult]
Controls logging for method return values.
[LogResult(Name = "Result")]
public int Calculate() => 42;
Properties:
Skip- Skip logging the return valueName- Custom name in logsMaxLength- Maximum length for string values
######### [LogException]
Controls exception logging behavior.
[LogException(LogLevel.Error)]
public void RiskyOperation() \{ }
Properties:
LogLevel- Log level for exceptions (default: Error)IncludeDetails- Include stack trace and inner exceptions (default: true)Rethrow- Rethrow the exception after logging (default: true)Message- Custom exception message template
######### [SensitiveData]
Marks data as sensitive, preventing it from being logged.
public void Login([SensitiveData] string password) \{ }
Properties:
MaskValue- The mask to use (default: "SENSITIVE")ShowLength- Show the length of sensitive data (default: false)
Dependency Injection Extensions
######### Service Registration with Logging
// Transient
services.AddTransientWithLogging<IMyService, MyService>();
// Scoped
services.AddScopedWithLogging<IMyService, MyService>();
// Singleton
services.AddSingletonWithLogging<IMyService, MyService>();
These extension methods automatically inject the IMethodLogger into your services.
Best Practices
- Use
partialclasses: Classes with logging attributes must be declared aspartial - Protect sensitive data: Always use
[SensitiveData]for passwords, tokens, and PII - Choose appropriate log levels: Use Debug for verbose logging, Information for normal flow, Warning for unusual situations
- Limit collection sizes: Set
MaxCollectionSizeto prevent logging large collections - Skip unnecessary logging: Use
Skip = truefor methods that don't need logging - Use structured logging: Enable
UseStructuredLoggingfor better log analysis
Performance Considerations
- Compile-time generation: All logging code is generated at compile time, not runtime
- Zero reflection: No reflection is used during logging execution
- Conditional logging: Logs are only formatted when the log level is enabled
- Minimal allocations: Optimized for low memory allocation
- Async-friendly: Async methods are properly handled without blocking
Requirements
- .NET 8.0 or .NET 10.0
- C# 11.0 or higher
- Microsoft.Extensions.Logging 8.0+
Sample Project
Check out the sample project for complete working examples demonstrating all features.
To run the sample:
cd samples/AOP.Logging.Sample
dotnet run
Versioning
This project follows Semantic Versioning and uses Conventional Commits for automated version management.
######### Release Process
- Automatic Versioning: Versions are calculated automatically based on commit messages
- Conventional Commits: All commits must follow the conventional commits specification
- semantic-release: Automated version calculation and release management
See VERSIONING.md for detailed information about our versioning strategy.
######### Commit Message Format
### Features (bumps MINOR version)
feat: add custom interceptor support
### Bug fixes (bumps PATCH version)
fix: resolve null reference in logger
### Breaking changes (bumps MAJOR version)
breaking: redesign attribute API
Contributing
Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
Important: All commits must follow Conventional Commits format for proper version management.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Roadmap
- Custom interceptor support
- Performance counters integration
- OpenTelemetry integration
- Configuration from appsettings.json
- Advanced filtering expressions
- Log correlation support
Made with ❤️ by the AOP.Logging community
About
Logging methods by generating new ones
How to use
Example (source csproj, source files)
- CSharp Project
- Program.cs
- Person.cs
This is the CSharp Project that references AOP.Logging.SourceGenerator
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AOP.Logging.Core" Version="1.4.0" />
<PackageReference Include="AOP.Logging.SourceGenerator" Version="1.4.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.11" />
</ItemGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
</Project>
This is the use of AOP.Logging.SourceGenerator in Program.cs
using AOP.Logging.Core.Configuration;
using AOP.Logging.Core.Logging;
using DemoLog;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
#region this is usually done by DI
using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
var logger = factory.CreateLogger<DefaultMethodLogger>();
IOptions<AopLoggingOptions> options = Options.Create(new AopLoggingOptions());
DefaultMethodLogger defaultMethodLogger = new(logger,options);
#endregion
Person p = new();
p.SetMethodLogger(defaultMethodLogger);
p.FirstName = "Andrei";
p.LastName = "Ignat";
Console.WriteLine (p.Name());
Console.WriteLine (await p.WithMiddleNameLogged("G"));
This is the use of AOP.Logging.SourceGenerator in Person.cs
using AOP.Logging.Core.Attributes;
using Microsoft.Extensions.Logging;
namespace DemoLog;
[LogClass]
internal partial class Person
{
public string FirstName \{ get; set; \} = string.Empty;
public string LastName \{ get; set; \} = string.Empty;
[LogMethod(LogLevel.Warning)]
public string NameCore() => $"{FirstName} {LastName}";
[LogMethod(LogLevel.Warning)]
private async Task<string> WithMiddleName(string middleName)
{
await Task.Delay(50);
return $"{FirstName} {middleName} {LastName}";
}
}
Generated Files
Those are taken from $(BaseIntermediateOutputPath)\GX
- Person_Logging.g.cs
// <auto-generated/>
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using AOP.Logging.Core.Interfaces;
namespace DemoLog
{
partial class Person
{
private IMethodLogger? __methodLogger;
/// <summary>
/// Sets the method logger for AOP logging.
/// This is typically called by the DI container.
/// </summary>
public void SetMethodLogger(IMethodLogger methodLogger)
{
__methodLogger = methodLogger;
}
/// <summary>
/// Logging wrapper for Name.
/// </summary>
public string Name()
{
if (__methodLogger != null)
{
var __parameters = new Dictionary<string, object?>();
__methodLogger.LogEntry("Person", "Name", __parameters, LogLevel.Warning);
}
var __stopwatch = Stopwatch.StartNew();
try
{
var __result = NameCore();
__stopwatch.Stop();
if (__methodLogger != null)
{
__methodLogger.LogExit("Person", "Name", __result, __stopwatch.ElapsedMilliseconds, LogLevel.Warning);
}
return __result;
}
catch (Exception __ex)
{
__stopwatch.Stop();
if (__methodLogger != null)
{
__methodLogger.LogException("Person", "Name", __ex, __stopwatch.ElapsedMilliseconds, LogLevel.Error);
}
throw;
}
}
/// <summary>
/// Logging wrapper for WithMiddleNameLogged.
/// </summary>
public async global::System.Threading.Tasks.Task<string> WithMiddleNameLogged(string middleName)
{
if (__methodLogger != null)
{
var __parameters = new Dictionary<string, object?>
{
\{ "middleName", middleName }
};
__methodLogger.LogEntry("Person", "WithMiddleNameLogged", __parameters, LogLevel.Warning);
}
var __stopwatch = Stopwatch.StartNew();
try
{
var __result = await WithMiddleName(middleName);
__stopwatch.Stop();
if (__methodLogger != null)
{
__methodLogger.LogExit("Person", "WithMiddleNameLogged", __result, __stopwatch.ElapsedMilliseconds, LogLevel.Warning);
}
return __result;
}
catch (Exception __ex)
{
__stopwatch.Stop();
if (__methodLogger != null)
{
__methodLogger.LogException("Person", "WithMiddleNameLogged", __ex, __stopwatch.ElapsedMilliseconds, LogLevel.Error);
}
throw;
}
}
}
}
Useful
Download Example (.NET C#)
Share AOP.Logging.SourceGenerator
https://ignatandrei.github.io/RSCG_Examples/v2/docs/AOP.Logging.SourceGenerator
Category "EnhancementClass" has the following generators:
1 AOP.Logging.SourceGenerator
2026-08-30
2 ApparatusAOT
2023-04-16
3 AspectGenerator
2024-01-07
4 CommonCodeGenerator
2024-04-03
5 Comparison
2025-05-25
6 DudNet
2023-10-27
7 Enhanced.GetTypes
2024-09-17
8 FastGenericNew
2023-08-10
9 Immutype
2023-08-12
10 Ling.Audit
2023-12-12
11 Lombok.NET
2023-04-16
12 M31.FluentAPI
2023-08-25
13 MemberAccessor
2025-03-24
14 MemoryPack
2023-08-04
15 Meziantou.Polyfill
2023-10-10
16 Microsoft.Extensions.Logging
2023-04-16
17 Microsoft.Extensions.Options.Generators.OptionsValidatorGenerator
2023-11-17
18 Microsoft.Interop.JavaScript.JSImportGenerator 2023-04-16
19 NLog.Extensions.ThisClass
2026-04-03
20 OptionToStringGenerator
2024-02-15
21 Pekspro.DataAnnotationValuesExtractor
2026-02-15
22 Program
2025-11-06
23 QueryStringGenerator
2024-11-07
24 RSCG_Decorator
2023-09-30
25 RSCG_UtilityTypes
2023-12-22
26 StaticReflection
2023-10-13
27 SyncMethodGenerator
2023-08-14
28 System.Runtime.InteropServices
2023-04-16
29 System.Text.RegularExpressions
2023-04-16
30 TelemetryLogging
2023-11-30
31 ThisClass
2024-04-19
32 TrimItEasy
2026-08-28