Mariodbx.AspectLogging
3.1.0
dotnet add package Mariodbx.AspectLogging --version 3.1.0
NuGet\Install-Package Mariodbx.AspectLogging -Version 3.1.0
<PackageReference Include="Mariodbx.AspectLogging" Version="3.1.0" />
<PackageVersion Include="Mariodbx.AspectLogging" Version="3.1.0" />
<PackageReference Include="Mariodbx.AspectLogging" />
paket add Mariodbx.AspectLogging --version 3.1.0
#r "nuget: Mariodbx.AspectLogging, 3.1.0"
#:package Mariodbx.AspectLogging@3.1.0
#addin nuget:?package=Mariodbx.AspectLogging&version=3.1.0
#tool nuget:?package=Mariodbx.AspectLogging&version=3.1.0
AspectLogging
A powerful Aspect-Oriented Programming (AOP) library for .NET that provides transparent, non-invasive logging and distributed tracing of method calls using dynamic proxies. AspectLogging enables comprehensive observability without modifying your business logic.
Table of Contents
- Overview
- Key Features
- Installation
- Quick Start
- Configuration
- Log Output Format
- Distributed Tracing
- HTTP Middleware
- Advanced Usage
- Sample Project
- API Reference
- System Requirements
- Contributing
- License
Overview
AspectLogging implements Aspect-Oriented Programming principles to separate cross-cutting concerns (logging, tracing) from business logic. It uses .NET's DispatchProxy to create dynamic proxies that intercept method calls, providing comprehensive logging and distributed tracing without requiring changes to your existing codebase.
Why AspectLogging?
- Zero Code Changes: Add observability without modifying your services
- Provider Agnostic: Works with any
ILoggerprovider (Console, Serilog, NLog, etc.) - Distributed Tracing: Full call graph correlation with parent/child spans
- Terminal Compatible: ASCII-based output works in all terminals
- Production Ready: Configurable presets for different environments
Key Features
| Feature | Description |
|---|---|
| Dynamic Proxy Interception | Automatically wraps service interfaces with logging |
| Async/Await Support | Full support for Task and Task<T> methods |
| Distributed Tracing | Parent/child span correlation across service calls |
| Timestamps | Configurable timestamps in log output |
| HTTP Middleware | Request/response logging with automatic span creation |
| Depth Visualization | Visual indentation showing call hierarchy |
| OpenTelemetry Compatible | Uses System.Diagnostics.Activity |
| Configurable Presets | Development, Balanced, Production, Minimal |
Installation
.NET CLI
dotnet add package Mariodbx.AspectLogging
Package Manager Console
Install-Package Mariodbx.AspectLogging
Package Reference
<PackageReference Include="Mariodbx.AspectLogging" Version="3.0.1" />
Quick Start
1. Configure Services
using AspectLogging;
var builder = WebApplication.CreateBuilder(args);
// Configure your logging provider (any ILogger-compatible provider works)
builder.Logging.AddConsole();
// Register your services
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddScoped<IOrderService, OrderService>();
// Add AspectLogging
builder.Services.AddAspectLogging(options =>
{
options.Enabled = true;
options.IncludeNamespacePrefixes = new[] { "MyApp.Services" };
// Logging options
options.LogEntry = true;
options.LogExit = true;
options.LogExceptions = true;
// Tracing options
options.Tracing.Enabled = true;
options.Tracing.ServiceName = "MyApp";
options.Tracing.IncludeTimestamp = true;
options.Tracing.IncludeSpanDepth = true;
});
var app = builder.Build();
// Enable HTTP request logging
app.UseLoggingMiddleware();
app.Run();
2. Use Preset Configuration
// Simple preset-based configuration
builder.Services.AddAspectLogging(LoggingPreset.Development);
// Preset with namespace filtering
builder.Services.AddAspectLogging(
LoggingPreset.Development,
"MyApp.Services",
"MyApp.Repositories");
Log Output Format
AspectLogging produces clear, ASCII-compatible log output:
14:32:15.123 [e0d6063f1e26:386805c03872] --> Enter IOrderService.PlaceOrderAsync (parent: N/A, depth: 0) with args []
14:32:15.125 [e0d6063f1e26:32319e266a10] --> Enter IPricingService.CalculatePrice (parent: 386805c03872, depth: 1) with args [productId: int]
14:32:15.130 [e0d6063f1e26:b4974870b49a] --> Enter IProductRepository.GetByIdAsync (parent: 32319e266a10, depth: 2) with args []
14:32:15.145 [e0d6063f1e26:b4974870b49a] <-- Exit IProductRepository.GetByIdAsync (parent: 32319e266a10, depth: 2, ms=15) with return Product
14:32:15.147 [e0d6063f1e26:32319e266a10] <-- Exit IPricingService.CalculatePrice (parent: 386805c03872, depth: 1, ms=22) with return Decimal
14:32:15.150 [e0d6063f1e26:386805c03872] <-- Exit IOrderService.PlaceOrderAsync (parent: N/A, depth: 0, ms=27) with return Order
Log Format Breakdown
| Component | Example | Description |
|---|---|---|
| Timestamp | 14:32:15.123 |
Time of the log entry (HH:mm:ss.fff) |
| TraceId | e0d6063f1e26 |
Unique ID for the entire request |
| SpanId | 386805c03872 |
Unique ID for this specific operation |
| Arrow | --> / <-- |
Entry (-->) or Exit (<--) |
| Method | IOrderService.PlaceOrderAsync |
Full method name |
| Parent | parent: 386805c03872 |
Parent span for correlation |
| Depth | depth: 1 |
Call hierarchy level (with indentation) |
| Duration | ms=22 |
Execution time in milliseconds |
| Return | with return Order |
Return type or value |
Exception Logging
14:32:15.200 [e0d6063f1e26:386805c03872] [X] Exception in IOrderService.PlaceOrderAsync (parent: N/A, depth: 0, ms=50)
System.InvalidOperationException: Order validation failed
at MyApp.Services.OrderService.PlaceOrderAsync()
Configuration
Full Configuration Options
builder.Services.AddAspectLogging(options =>
{
// === Core Options ===
options.Enabled = true; // Enable/disable globally
options.IncludeNamespacePrefixes = new[] { "MyApp" }; // Namespaces to intercept
options.ExcludedServiceTypeFullNames = new[] { "MyApp.IHealthCheck" };
options.IgnoredMethods = new[] { "ToString", "GetHashCode" };
// === Logging Behavior ===
options.LogEntry = true; // Log method entry
options.LogExit = true; // Log method exit
options.LogExceptions = true; // Log exceptions
options.LogDetailedArgs = false; // Log argument values
options.LogDetailedReturns = false; // Log return values
options.IncludeParameterNames = true; // Include param names
// === Log Levels ===
options.EntryLogLevel = LogLevel.Information;
options.ExitLogLevel = LogLevel.Information;
options.ExceptionLogLevel = LogLevel.Error;
// === Performance ===
options.MinimumDurationMs = 0; // Min duration to log
options.SlowExecutionThresholdMs = 1000; // Warn level threshold
options.MaxReturnValueLength = 500; // Truncate long values
// === Tracing Options ===
options.Tracing.Enabled = true; // Enable distributed tracing
options.Tracing.ServiceName = "MyApp"; // Service identifier
options.Tracing.IncludeTimestamp = true; // Add timestamp to logs
options.Tracing.IncludeSpanDepth = true; // Show depth with indent
options.Tracing.MaxSpanDepth = 10; // Max trace depth
options.Tracing.CreateMethodSpans = true; // Create spans for methods
options.Tracing.CreateHttpSpans = true; // Create spans for HTTP
options.Tracing.PushToLogContext = true; // Add to ILogger scope
// === Middleware Options ===
options.Middleware.Enabled = true;
options.Middleware.LogRequestStart = true;
options.Middleware.LogRequestEnd = true;
options.Middleware.LogHeaders = false;
options.Middleware.LogRequestBody = false;
options.Middleware.LogResponseBody = false;
options.Middleware.ExcludedPaths = new[] { "/health", "/swagger*", "*.css", "*.js" };
});
JSON Configuration
{
"AspectLogging": {
"Enabled": true,
"IncludeNamespacePrefixes": ["MyApp.Services", "MyApp.Repositories"],
"LogEntry": true,
"LogExit": true,
"LogExceptions": true,
"EntryLogLevel": "Information",
"ExitLogLevel": "Information",
"ExceptionLogLevel": "Error",
"Tracing": {
"Enabled": true,
"ServiceName": "MyApp",
"IncludeTimestamp": true,
"IncludeSpanDepth": true,
"MaxSpanDepth": 10,
"PushToLogContext": true
},
"Middleware": {
"Enabled": true,
"ExcludedPaths": ["/health", "/swagger*"]
}
}
}
builder.Services.AddAspectLogging(builder.Configuration);
Preset Configurations
| Preset | Entry | Exit | Args | Returns | Exceptions | Tracing | Min Duration |
|---|---|---|---|---|---|---|---|
| Development | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0ms |
| Balanced | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ | 0ms |
| Production | ✗ | ✓ | ✗ | ✗ | ✓ | ✓ | 100ms |
| Minimal | ✗ | ✗ | ✗ | ✗ | ✓ | ✗ | 1000ms |
Distributed Tracing
Call Graph Visualization
AspectLogging tracks the complete call hierarchy:
HTTP POST /api/orders [Depth 0]
└── IOrderService.PlaceOrderAsync [Depth 1]
├── IOrderValidationService.ValidateAsync [Depth 2]
│ ├── IProductRepository.GetByIdAsync [Depth 3]
│ └── IStockService.CheckAvailabilityAsync [Depth 3]
├── IPricingService.CalculateTotalAsync [Depth 2]
│ ├── IDiscountService.ApplyDiscountsAsync [Depth 3]
│ └── ITaxService.CalculateTaxAsync [Depth 3]
└── IPaymentService.ProcessPaymentAsync [Depth 2]
Span Properties
| Property | Description |
|---|---|
TraceId |
Unique ID shared across all spans in a request |
SpanId |
Unique ID for this specific operation |
ParentSpanId |
ID of the parent span (null for root) |
Depth |
Nesting level (0 = HTTP request) |
OperationName |
Method or operation name |
DurationMs |
Execution time in milliseconds |
OpenTelemetry Integration
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddSource("AspectLogging")
.AddJaegerExporter()
.AddZipkinExporter());
HTTP Middleware
Basic Usage
app.UseLoggingMiddleware();
Middleware Output
14:32:15.100 [e0d6063f1e26:root] HTTP POST /api/orders - Request started
14:32:15.150 [e0d6063f1e26:root] HTTP POST /api/orders completed: 200 in 50ms
Middleware Configuration
options.Middleware.Enabled = true;
options.Middleware.LogRequestStart = true;
options.Middleware.LogRequestEnd = true;
options.Middleware.LogHeaders = false; // Log request headers
options.Middleware.LogRequestBody = false; // Log request body (careful with sensitive data)
options.Middleware.LogResponseBody = false; // Log response body
options.Middleware.IncludeTraceId = true;
options.Middleware.ExcludedPaths = new[]
{
"/health",
"/swagger*",
"/favicon.ico",
"*.css",
"*.js"
};
Advanced Usage
Excluding Methods with [NoLog]
public interface IUserService
{
Task<User> GetUserAsync(int id);
[NoLog] // This method won't be logged
Task<string> GetPasswordHashAsync(int userId);
}
// Exclude entire interface
[NoLog]
public interface ISensitiveDataService
{
Task<string> GetSsnAsync(int userId);
}
Custom Tracing Tags
// In your service method
TracingContext.SetTag("customer.id", customerId);
TracingContext.SetTag("order.total", orderTotal);
Manual Span Creation
using (var scope = TracingContext.StartMethodSpan("CustomOperation"))
{
// Your code here
// Span automatically timed and closed
}
Accessing Current Trace Info
var traceId = TracingContext.CurrentTraceId;
var spanId = TracingContext.CurrentSpanId;
var depth = TracingContext.CurrentDepth;
Integrating with Entity Framework Logging
To see database commands between your service logs:
services.AddDbContext<YourDbContext>(options =>
{
options.UseSqlServer(connectionString)
.UseLoggerFactory(loggerFactory)
.EnableSensitiveDataLogging(); // Shows parameter values
});
This produces interleaved output:
14:32:15.130 [e0d6063f1e26:b497] --> Enter IProductRepository.GetByIdAsync (parent: 3231, depth: 2)
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (5ms) SELECT * FROM Products WHERE Id = @p0
14:32:15.145 [e0d6063f1e26:b497] <-- Exit IProductRepository.GetByIdAsync (parent: 3231, depth: 2, ms=15)
Sample Project
A comprehensive sample is included: samples/AspectLogging.Sample.WebApi
cd samples/AspectLogging.Sample.WebApi
dotnet run
Navigate to http://localhost:5000 for:
- Dashboard with statistics
- Products listing
- Tracing demo
- Pricing calculator
- Order flow demonstration
API Reference
ServiceCollectionExtensions
// Preset configuration
IServiceCollection AddAspectLogging(LoggingPreset preset)
IServiceCollection AddAspectLogging(LoggingPreset preset, params string[] namespaces)
// Lambda configuration
IServiceCollection AddAspectLogging(Action<LoggingProxyOptions> configure)
// Configuration binding
IServiceCollection AddAspectLogging(IConfiguration configuration)
TracingContext
static SpanInfo? Current { get; }
static string? CurrentTraceId { get; }
static string? CurrentSpanId { get; }
static int CurrentDepth { get; }
static ActivitySource ActivitySource { get; }
static TracingScope StartHttpSpan(HttpContext context, string? serviceName)
static TracingScope StartMethodSpan(string operationName, SpanKind kind)
static void SetTag(string key, object? value)
Attributes
[NoLog] // Exclude method or interface from logging
System Requirements
- .NET: 8.0 or 9.0
- Dependencies:
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
Contributing
Contributions are welcome! Please read CONTRIBUTING.md for guidelines.
# Clone the repository
git clone https://github.com/mariodbx/aspect-logging.git
cd aspect-logging
# Build
dotnet build
# Run tests (208 tests)
dotnet test
# Run sample
cd samples/AspectLogging.Sample.WebApi
dotnet run
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
- GitHub Issues: https://github.com/mariodbx/aspect-logging/issues
- NuGet Package: https://www.nuget.org/packages/Mariodbx.AspectLogging
Version: 3.0.1
Author: Mario De Benedictis
Repository: https://github.com/mariodbx/aspect-logging
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.AspNetCore.Http (>= 2.2.2)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
-
net9.0
- Microsoft.AspNetCore.Http (>= 2.2.2)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
v1.1.0 - Distributed Tracing
- Add TracingContext, SpanInfo, TracingScope for span hierarchy management
- Add TracingOptions for tracing configuration
- Enhance LoggingMiddleware to create root spans for HTTP requests
- Enhance LoggingProxy to create child spans for service method calls
- OpenTelemetry compatibility via System.Diagnostics.Activity
- Comprehensive sample project with MVC frontend
v1.0.0 - Initial release
- Dynamic proxy-based method interception
- Configurable via code or appsettings.json
- Async method support
- Custom type and value formatting
- HTTP middleware for request/response logging
- Performance thresholds and filtering
- Structured logging support