Skip to main content

AOP.Logging.SourceGenerator by Dietmar Borgards

NuGet / site data

Nuget GitHub last commit GitHub Repo stars

Details

Info

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

Source: https://github.com/dborgards/aop.logging.net

Author

note

Dietmar Borgards Alt text

Original Readme

note

AOP.Logging.NET

License: MIT .NET NuGet Semantic Release

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 Core generate wrappers without the suffix (e.g., AddCoreAdd)
  • No Core Pattern: Methods without Core generate wrappers with Logged suffix (e.g., CreateOrderCreateOrderLogged)
  • 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 *Core methods (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 Logged suffix (e.g., CreateOrderCreateOrderLogged).

######### 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 method
  • LogExecutionTime - Track execution time
  • LogParameters - Log method parameters
  • LogReturnValue - Log return value
  • LogExceptions - Log exceptions
  • Skip - Skip logging for this method
  • EntryMessage - Custom entry message template
  • ExitMessage - Custom exit message template

######### [LogParameter]

Controls logging for a specific parameter.

public void MyMethod([LogParameter(Name = "UserId")] int id) \{ }

Properties:

  • Skip - Skip logging this parameter
  • Name - Custom name in logs
  • MaxLength - 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 value
  • Name - Custom name in logs
  • MaxLength - 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
  1. Use partial classes: Classes with logging attributes must be declared as partial
  2. Protect sensitive data: Always use [SensitiveData] for passwords, tokens, and PII
  3. Choose appropriate log levels: Use Debug for verbose logging, Information for normal flow, Warning for unusual situations
  4. Limit collection sizes: Set MaxCollectionSize to prevent logging large collections
  5. Skip unnecessary logging: Use Skip = true for methods that don't need logging
  6. Use structured logging: Enable UseStructuredLogging for 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
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

note

Logging methods by generating new ones

How to use

Example (source csproj, source files)

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>

Generated Files

Those are taken from $(BaseIntermediateOutputPath)\GX

// <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 Nuget GitHub Repo stars 2026-08-30

2 ApparatusAOT Nuget GitHub Repo stars 2023-04-16

3 AspectGenerator Nuget GitHub Repo stars 2024-01-07

4 CommonCodeGenerator Nuget GitHub Repo stars 2024-04-03

5 Comparison Nuget GitHub Repo stars 2025-05-25

6 DudNet Nuget GitHub Repo stars 2023-10-27

7 Enhanced.GetTypes Nuget GitHub Repo stars 2024-09-17

8 FastGenericNew Nuget GitHub Repo stars 2023-08-10

9 Immutype Nuget GitHub Repo stars 2023-08-12

10 Ling.Audit Nuget GitHub Repo stars 2023-12-12

11 Lombok.NET Nuget GitHub Repo stars 2023-04-16

12 M31.FluentAPI Nuget GitHub Repo stars 2023-08-25

13 MemberAccessor Nuget GitHub Repo stars 2025-03-24

14 MemoryPack Nuget GitHub Repo stars 2023-08-04

15 Meziantou.Polyfill Nuget GitHub Repo stars 2023-10-10

16 Microsoft.Extensions.Logging Nuget GitHub Repo stars 2023-04-16

17 Microsoft.Extensions.Options.Generators.OptionsValidatorGenerator Nuget GitHub Repo stars 2023-11-17

18 Microsoft.Interop.JavaScript.JSImportGenerator 2023-04-16

19 NLog.Extensions.ThisClass Nuget GitHub Repo stars 2026-04-03

20 OptionToStringGenerator Nuget GitHub Repo stars 2024-02-15

21 Pekspro.DataAnnotationValuesExtractor Nuget GitHub Repo stars 2026-02-15

22 Program Nuget GitHub Repo stars 2025-11-06

23 QueryStringGenerator Nuget GitHub Repo stars 2024-11-07

24 RSCG_Decorator Nuget GitHub Repo stars 2023-09-30

25 RSCG_UtilityTypes NugetNuget GitHub Repo stars 2023-12-22

26 StaticReflection NugetNuget GitHub Repo stars 2023-10-13

27 SyncMethodGenerator Nuget GitHub Repo stars 2023-08-14

28 System.Runtime.InteropServices Nuget GitHub Repo stars 2023-04-16

29 System.Text.RegularExpressions Nuget GitHub Repo stars 2023-04-16

30 TelemetryLogging Nuget GitHub Repo stars 2023-11-30

31 ThisClass Nuget GitHub Repo stars 2024-04-19

32 TrimItEasy Nuget GitHub Repo stars 2026-08-28

See category

EnhancementClass