A modern structured logging library for .NET with support for custom enrichers, JSON output, and full Dependency Injection integration.
Why StructLog? Standard
ILogger<T>lets you log anything, but doesn't enforce a consistent structure. StructLog requires every log entry to carry an EventCode β a short, queryable identifier that makes filtering and alerting in production trivial.
- π§ Structured JSON output β every log entry is a consistent, queryable JSON object
- π·οΈ EventCode β mandatory code + description on every entry, great for monitoring and alerting
- β‘ Custom Enrichers β inject contextual data (machine name, request ID, tenant, β¦) via
ILogEnricher - π DI-ready β works with
ILogger<T>andIOptions<StructLogOptions>out of the box - π Configurable via
appsettings.json - π₯οΈ Multi-host β Console, Worker Service, and ASP.NET Core apps
| Requirement | Minimum version |
|---|---|
| .NET | 9.0+ |
dotnet add package StructLog{
"StructLog": {
"EnableEnrichers": true
}
}// With appsettings.json
builder.Services.AddStructLog(builder.Configuration);
// Without appsettings (uses defaults)
builder.Services.AddStructLog();[ApiController]
public class UsersController : ControllerBase
{
private readonly IStructLog<UsersController> _logger;
public UsersController(IStructLog<UsersController> logger)
{
_logger = logger;
}
[HttpGet("{id}")]
public async Task<IActionResult> GetUser(int id)
{
var user = await GetUserFromDb(id);
if (user is null)
{
_logger.Warning(
"User not found",
new EventCode("USR_001", "Get user"),
new { UserId = id });
return NotFound();
}
_logger.Info(
"User retrieved successfully",
new EventCode("USR_002", "Get user"),
user);
return Ok(user);
}
}{
"EventCode": "USR_002",
"EventDescription": "Get user",
"Message": "User retrieved successfully",
"Context": {
"MachineName": "server-01"
},
"Data": {
"Id": 1,
"Name": "Simone",
"Surname": "Rossi",
"Birthday": "1993-05-12T00:00:00"
}
}All methods accept an IEventCode and an optional data object.
_logger.Trace("Entering method", eventCode);
_logger.Debug("Cache miss", eventCode, new { Key = "users:1" });
_logger.Info("User created", eventCode, user);
_logger.Warning("Retry attempt", eventCode, new { Attempt = 3 });
_logger.Error("Payment failed", eventCode, exception, new { OrderId = 42 });
_logger.Critical("DB unreachable", eventCode, exception);Use BeginScope to attach contextual data to all logs within a block:
using (_logger.BeginScope(new Dictionary<string, object>
{
["RequestId"] = HttpContext.TraceIdentifier,
["UserId"] = currentUserId
}))
{
_logger.Info("Processing request", new EventCode("REQ_001", "Request"));
}EventCode is the core of StructLog. Every log entry must carry one, which makes your logs
immediately searchable and alertable in tools like Kibana, Grafana, or Azure Monitor.
// Simple inline usage
var code = new EventCode("PAY_003", "Payment processing");
// Recommended: define codes as constants in a dedicated class
public static class LogEvents
{
public static readonly EventCode UserCreated = new("USR_001", "User lifecycle");
public static readonly EventCode UserDeleted = new("USR_002", "User lifecycle");
public static readonly EventCode PaymentFailed = new("PAY_001", "Payment processing");
}
// Usage
_logger.Error("Payment declined", LogEvents.PaymentFailed, exception, new { OrderId = 99 });Enrichers automatically attach contextual data to every log entry.
Implement ILogEnricher and register it with DI:
// 1. Implement the interface
public class MachineNameEnricher : ILogEnricher
{
public void Enrich(IDictionary<string, object> context)
{
context["MachineName"] = Environment.MachineName;
}
}
// 2. Register β all ILogEnricher implementations are picked up automatically
builder.Services.AddSingleton<ILogEnricher, MachineNameEnricher>();
// 3. Enable in appsettings.json
// "StructLog": { "EnableEnrichers": true }You can register as many enrichers as you need. Common use cases: machine name, environment, request ID, tenant ID, application version.
| Option | Type | Default | Description |
|---|---|---|---|
EnableEnrichers |
bool |
true |
Whether enrichers are invoked for each log entry |
If you find StructLog useful, consider sponsoring its development on GitHub Sponsors. Even a small contribution helps keep the project maintained and growing.
MIT β see LICENSE for details.