Skip to content

Repository files navigation

AutoLog.Generator

NuGetNuGet DownloadsCILicense: 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.

Why AutoLog?

Microsoft's built-in [LoggerMessage] pattern is fast, but it pushes a lot of ceremony into your code:

  • static partial methods
  • 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 a partial void method
  • Use your existing ILogger field or property
  • Auto-detects ILogger / ILogger<T>
  • Auto-assigns EventIds per class
  • AOT-safe and zero-reflection

Installation

dotnet add package AutoLog.Generator

Then add a normal ILogger field or property to your partial class.

Quick start

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);}

Comparison with Microsoft's [LoggerMessage]

[LoggerMessage]

usingMicrosoft.Extensions.Logging;publicstaticpartialclassOrderLogs{[LoggerMessage(EventId=1001,Level=LogLevel.Information,Message="Processing order {OrderId} for customer {CustomerId}")]publicstaticpartialvoidProcessingOrder(ILoggerlogger,intorderId,stringcustomerId);}

AutoLog

usingAutoLog;usingMicrosoft.Extensions.Logging;publicpartialclassOrderService{privatereadonlyILogger<OrderService>_logger;[Log(LogLevel.Information,"Processing order {OrderId} for customer {CustomerId}")]partialvoidLogProcessingOrder(intorderId,stringcustomerId);}

Why it feels simpler

Concern[LoggerMessage]AutoLog
Method shapestatic partialinstance partial void
Logger parameterexplicit in every methodauto-detected from class
Event IDsmanually managedsequential per class
Boilerplatemediumlow
Generated performanceexcellentexcellent

Exception parameter handling

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);

Rules

  • [Log] only works on partial void methods
  • The method must live on a partial class
  • The containing class must expose an instance ILogger or ILogger<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

Diagnostics

CodeSeverityMessage
AL001Error[Log] on {Method} in {Class} — method must be partial void.
AL002Error[Log] on {Method} — containing class {Class} has no ILogger or ILogger<T> field or property. Add one to enable log generation.
AL003Warning[Log] on {Method} has {N} type parameters — LoggerMessage.Define supports a maximum of 6.

Migrating from [LoggerMessage]

Microsoft's source-generated [LoggerMessage] and AutoLog produce the same LoggerMessage.Define output. The difference is ergonomics.

Before ([LoggerMessage])

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);

After (AutoLog)

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);

Migration steps

  1. InstallAutoLog.Generatordotnet add package AutoLog.Generator
  2. Make your service class partial
  3. Replace static partial log methods with partial void instance methods and [Log]
  4. Remove the explicit ILogger parameter — AutoLog detects it from the field
  5. Remove manual EventIds — auto-assigned sequentially per class
  6. Update call sites — drop ClassName. prefix and the logger argument

Also by the same author

🌐 Full suite overview: swevo.github.io

PackageDescription
AutoHttpClient.GeneratorCompile-time typed HTTP client — [HttpClient] on an interface generates a strongly-typed client. AOT-safe Refit alternative.
AutoDispatch.GeneratorCompile-time CQRS dispatcher — [Handler] generates a strongly-typed IDispatcher. No MediatR, no reflection.
AutoWireCompile-time DI auto-registration — [Scoped]/[Singleton]/[Transient] generates IServiceCollection registration code.
AutoMap.GeneratorCompile-time object mapping with generated extension methods. AOT-safe AutoMapper alternative.
AutoValidate.GeneratorCompile-time FluentValidation wiring — discovers validators and generates AddValidators().
AutoResult.GeneratorCompile-time Result<T>[TryWrap] generates Try*() wrappers for every public method.
AutoQuery.GeneratorCompile-time LINQ query specs — [QuerySpec] generates a strongly-typed Apply(IQueryable<T>).

Related Packages

PackageDownloadsDescription
AutoWireDownloadsCompile-time dependency injection auto-registration for
AutoMap.GeneratorDownloadsCompile-time object mapping for
AutoQuery.GeneratorDownloadsCompile-time query composition for IQueryable using Roslyn incremental source generators
AutoArchitectureDownloadsCompile-time architecture/dependency-rule enforcement for
AutoHttpClient.GeneratorDownloadsCompile-time typed HTTP client generation for
AutoDispatch.GeneratorDownloadsCompile-time CQRS dispatcher for
AutoValidate.GeneratorDownloadsCompile-time FluentValidation wiring for

License

MIT

About

Compile-time high-performance logging for .NET — [Log(Level, Message)] on a partial method generates LoggerMessage.Define at build time. Zero reflection, AOT-safe.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages