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.GeneratorThen add a normal ILogger field or property to your partial class.
usingAutoLog;usingMicrosoft.Extensions.Logging;publicpartialclassOrderService{privatereadonlyILogger<OrderService>_logger;publicOrderService(ILogger<OrderService>logger)=>_logger=logger;[Log(LogLevel.Information,"Processing order {OrderId} for customer {CustomerId}")]partialvoidLogProcessingOrder(intorderId,stringcustomerId);[Log(LogLevel.Warning,"Order {OrderId} not found")]partialvoidLogOrderNotFound(intorderId);[Log(LogLevel.Error,"Failed to process order {OrderId}")]partialvoidLogProcessingFailed(intorderId,Exceptionex);}AutoLog generates code like:
// <auto-generated by AutoLog.Generator/>
#nullable enable
partialclassOrderService{privatestaticreadonlyglobal::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,newglobal::Microsoft.Extensions.Logging.EventId(1,"LogProcessingOrder"),"Processing order {OrderId} for customer {CustomerId}");partialvoidLogProcessingOrder(intorderId,stringcustomerId)=>_logProcessingOrderAction(_logger,orderId,customerId,null);}usingMicrosoft.Extensions.Logging;publicstaticpartialclassOrderLogs{[LoggerMessage(EventId=1001,Level=LogLevel.Information,Message="Processing order {OrderId} for customer {CustomerId}")]publicstaticpartialvoidProcessingOrder(ILoggerlogger,intorderId,stringcustomerId);}usingAutoLog;usingMicrosoft.Extensions.Logging;publicpartialclassOrderService{privatereadonlyILogger<OrderService>_logger;[Log(LogLevel.Information,"Processing order {OrderId} for customer {CustomerId}")]partialvoidLogProcessingOrder(intorderId,stringcustomerId);}| 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}")]partialvoidLogProcessingFailed(intorderId,Exceptionex);Generates:
privatestaticreadonlyglobal::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,newglobal::Microsoft.Extensions.Logging.EventId(1,"LogProcessingFailed"),"Failed to process order {OrderId}");partialvoidLogProcessingFailed(intorderId,Exceptionex)=>_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.
publicstaticpartialclassOrderLogs{[LoggerMessage(EventId=1001,Level=LogLevel.Information,Message="Processing order {OrderId} for customer {CustomerId}")]publicstaticpartialvoidProcessingOrder(ILoggerlogger,intorderId,stringcustomerId);[LoggerMessage(EventId=1002,Level=LogLevel.Warning,Message="Order {OrderId} not found")]publicstaticpartialvoidOrderNotFound(ILoggerlogger,intorderId);}// Call site — must pass logger explicitly every time:OrderLogs.ProcessingOrder(_logger,order.Id,order.CustomerId);publicpartialclassOrderService{privatereadonlyILogger<OrderService>_logger;[Log(LogLevel.Information,"Processing order {OrderId} for customer {CustomerId}")]partialvoidLogProcessingOrder(intorderId,stringcustomerId);[Log(LogLevel.Warning,"Order {OrderId} not found")]partialvoidLogOrderNotFound(intorderId);}// 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 | Compile-time dependency injection auto-registration for | |
| AutoMap.Generator | Compile-time object mapping for | |
| AutoQuery.Generator | Compile-time query composition for IQueryable using Roslyn incremental source generators | |
| AutoArchitecture | Compile-time architecture/dependency-rule enforcement for | |
| AutoHttpClient.Generator | Compile-time typed HTTP client generation for | |
| AutoDispatch.Generator | Compile-time CQRS dispatcher for | |
| AutoValidate.Generator | Compile-time FluentValidation wiring for |
MIT