NuGet NuGet Downloads CI License: MIT .NET 10 Ready
AutoLog turns simple [Log]-annotated partial methods into high-performance LoggerMessage.Define logging at build time. You keep normal ILogger fields and readable structured log messages; the generator emits cached delegates, EventIds, and partial method bodies for you.
Microsoft's built-in [LoggerMessage] pattern is fast, but it pushes a lot of ceremony into your code:
static partialmethods- explicit logger parameters
- manual event IDs
- more boilerplate around every log statement
AutoLog keeps the performance benefits while simplifying the authoring model:
- Just add
[Log]to apartial voidmethod - Use your existing
ILoggerfield or property - Auto-detects
ILogger/ILogger<T> - Auto-assigns
EventIds per class - AOT-safe and zero-reflection
dotnet add package AutoLog.Generator
Then add a normal ILogger field or property to your partial class.
using AutoLog; using Microsoft.Extensions.Logging; public partial class OrderService { private readonly ILogger<OrderService> _logger; public OrderService(ILogger<OrderService> logger) => _logger = logger; [Log(LogLevel.Information, "Processing order {OrderId} for customer {CustomerId}")] partial void LogProcessingOrder(int orderId, string customerId); [Log(LogLevel.Warning, "Order {OrderId} not found")] partial void LogOrderNotFound(int orderId); [Log(LogLevel.Error, "Failed to process order {OrderId}")] partial void LogProcessingFailed(int orderId, Exception ex); }
AutoLog generates code like:
// <auto-generated by AutoLog.Generator/> #nullable enable partial class OrderService { private static readonly global::System.Action<global::Microsoft.Extensions.Logging.ILogger, int, string, global::System.Exception?> _logProcessingOrderAction = global::Microsoft.Extensions.Logging.LoggerMessage.Define<int, string>( global::Microsoft.Extensions.Logging.LogLevel.Information, new global::Microsoft.Extensions.Logging.EventId(1, "LogProcessingOrder"), "Processing order {OrderId} for customer {CustomerId}"); partial void LogProcessingOrder(int orderId, string customerId) => _logProcessingOrderAction(_logger, orderId, customerId, null); }
using Microsoft.Extensions.Logging; public static partial class OrderLogs { [LoggerMessage( EventId = 1001, Level = LogLevel.Information, Message = "Processing order {OrderId} for customer {CustomerId}")] public static partial void ProcessingOrder( ILogger logger, int orderId, string customerId); }
using AutoLog; using Microsoft.Extensions.Logging; public partial class OrderService { private readonly ILogger<OrderService> _logger; [Log(LogLevel.Information, "Processing order {OrderId} for customer {CustomerId}")] partial void LogProcessingOrder(int orderId, string customerId); }
| Concern | [LoggerMessage] |
AutoLog |
|---|---|---|
| Method shape | static partial |
instance partial void |
| Logger parameter | explicit in every method | auto-detected from class |
| Event IDs | manually managed | sequential per class |
| Boilerplate | medium | low |
| Generated performance | excellent | excellent |
If the last parameter is Exception, AutoLog maps it to the exception slot of LoggerMessage.Define instead of treating it as a template parameter:
[Log(LogLevel.Error, "Failed to process order {OrderId}")] partial void LogProcessingFailed(int orderId, Exception ex);
Generates:
private static readonly global::System.Action<global::Microsoft.Extensions.Logging.ILogger, int, global::System.Exception?> _logProcessingFailedAction = global::Microsoft.Extensions.Logging.LoggerMessage.Define<int>( global::Microsoft.Extensions.Logging.LogLevel.Error, new global::Microsoft.Extensions.Logging.EventId(1, "LogProcessingFailed"), "Failed to process order {OrderId}"); partial void LogProcessingFailed(int orderId, Exception ex) => _logProcessingFailedAction(_logger, orderId, ex);
[Log]only works onpartial voidmethods- The method must live on a
partial class - The containing class must expose an instance
ILoggerorILogger<T>field/property - AutoLog uses the first matching logger member it finds
LoggerMessage.Define<T1..T6>supports up to 6 non-exception parameters- If a log method exceeds 6 parameters, AutoLog emits AL003 and falls back to an object-array logging path
| Code | Severity | Message |
|---|---|---|
| AL001 | Error | [Log] on {Method} in {Class} — method must be partial void. |
| AL002 | Error | [Log] on {Method} — containing class {Class} has no ILogger or ILogger<T> field or property. Add one to enable log generation. |
| AL003 | Warning | [Log] on {Method} has {N} type parameters — LoggerMessage.Define supports a maximum of 6. |
Microsoft's source-generated [LoggerMessage] and AutoLog produce the same LoggerMessage.Define output. The difference is ergonomics.
public static partial class OrderLogs { [LoggerMessage(EventId = 1001, Level = LogLevel.Information, Message = "Processing order {OrderId} for customer {CustomerId}")] public static partial void ProcessingOrder(ILogger logger, int orderId, string customerId); [LoggerMessage(EventId = 1002, Level = LogLevel.Warning, Message = "Order {OrderId} not found")] public static partial void OrderNotFound(ILogger logger, int orderId); } // Call site — must pass logger explicitly every time: OrderLogs.ProcessingOrder(_logger, order.Id, order.CustomerId);
public partial class OrderService { private readonly ILogger<OrderService> _logger; [Log(LogLevel.Information, "Processing order {OrderId} for customer {CustomerId}")] partial void LogProcessingOrder(int orderId, string customerId); [Log(LogLevel.Warning, "Order {OrderId} not found")] partial void LogOrderNotFound(int orderId); } // Call site — logger is implicit: LogProcessingOrder(order.Id, order.CustomerId);
- Install
AutoLog.Generator—dotnet add package AutoLog.Generator - Make your service class
partial - Replace
static partiallog methods withpartial voidinstance methods and[Log] - Remove the explicit
ILoggerparameter — AutoLog detects it from the field - Remove manual
EventIds — auto-assigned sequentially per class - Update call sites — drop
ClassName.prefix and the logger argument
🌐 Full suite overview: swevo.github.io
| Package | Description |
|---|---|
| AutoHttpClient.Generator | Compile-time typed HTTP client — [HttpClient] on an interface generates a strongly-typed client. AOT-safe Refit alternative. |
| AutoDispatch.Generator | Compile-time CQRS dispatcher — [Handler] generates a strongly-typed IDispatcher. No MediatR, no reflection. |
| AutoWire | Compile-time DI auto-registration — [Scoped]/[Singleton]/[Transient] generates IServiceCollection registration code. |
| AutoMap.Generator | Compile-time object mapping with generated extension methods. AOT-safe AutoMapper alternative. |
| AutoValidate.Generator | Compile-time FluentValidation wiring — discovers validators and generates AddValidators(). |
| AutoResult.Generator | Compile-time Result<T> — [TryWrap] generates Try*() wrappers for every public method. |
| AutoQuery.Generator | Compile-time LINQ query specs — [QuerySpec] generates a strongly-typed Apply(IQueryable<T>). |
| Package | Downloads | Description |
|---|---|---|
| AutoWire | Downloads | Compile-time dependency injection auto-registration for |
| AutoMap.Generator | Downloads | Compile-time object mapping for |
| AutoQuery.Generator | Downloads | Compile-time query composition for IQueryable using Roslyn incremental source generators |
| AutoArchitecture | Downloads | Compile-time architecture/dependency-rule enforcement for |
| AutoHttpClient.Generator | Downloads | Compile-time typed HTTP client generation for |
| AutoDispatch.Generator | Downloads | Compile-time CQRS dispatcher for |
| AutoValidate.Generator | Downloads | Compile-time FluentValidation wiring for |
MIT