Skip to content
Back to skills

Logging

ASecurity

Serilog structured logging for ASP.NET Core — setup, message templates, LogContext enrichment, request logging middleware, and log level guidelines.

  • 5 stars
  • 0 votes
  • 0 copies
  • 7 views
  • Added May 28, 2026
ai-agentssqldebuggingapidatabasesecurityperformance

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 23, 2026

npx -y skills add zdanovichnick/dotnet-pilot --skill logging --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Logging?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Logging
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/zdanovichnick-logging/badge)](https://www.skillsdirectory.com/skills/zdanovichnick-logging)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: logging
description: Serilog structured logging for ASP.NET Core — setup, message templates, LogContext enrichment, request logging middleware, and log level guidelines.
when_to_use: Adding Serilog to a service, writing or reviewing log statements, or checking what reaches a log sink.
---

# Serilog Structured Logging

Reference material for adding and using Serilog in ASP.NET Core.

## NuGet Packages

```xml
<PackageReference Include="Serilog.AspNetCore" Version="8.*" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.*" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.*" />
<PackageReference Include="Serilog.Formatting.Compact" Version="3.*" />
<!-- Add sinks as needed: -->
<PackageReference Include="Serilog.Sinks.OpenTelemetry" Version="4.*" />
```

## Setup — Program.cs

Replace the default ASP.NET Core logging before `builder.Build()`:

```csharp
builder.Host.UseSerilog((context, config) =>
    config
        .ReadFrom.Configuration(context.Configuration)
        .Enrich.FromLogContext()
        .Enrich.WithMachineName()
        .Enrich.WithThreadId()
        .WriteTo.Console(new CompactJsonFormatter())
        .WriteTo.OpenTelemetry()); // swap/add sinks as needed
```

For early startup errors, bootstrap a minimal logger before the host is built:

```csharp
Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateBootstrapLogger();
try
{
    var builder = WebApplication.CreateBuilder(args);
    builder.Host.UseSerilog((ctx, cfg) => cfg.ReadFrom.Configuration(ctx.Configuration)...);
    // ...
}
catch (Exception ex)
{
    Log.Fatal(ex, "Application startup failed.");
}
finally
{
    Log.CloseAndFlush();
}
```

## appsettings.json Configuration

```json
{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "Microsoft.EntityFrameworkCore": "Warning",
        "Microsoft.EntityFrameworkCore.Database.Command": "Warning",
        "System": "Warning"
      }
    }
  }
}
```

Flip EF Core's `Database.Command` override to `Information` temporarily when debugging query issues; revert before shipping.

## Structured Logging — Message Templates

Always use **message templates**, never string interpolation. Interpolation loses the structured property; templates capture it.

```csharp
// CORRECT — structured properties captured
_logger.LogInformation("Order {OrderId} created for customer {CustomerId}", order.Id, order.CustomerId);
_logger.LogWarning("Payment failed for order {OrderId}: {Reason}", orderId, reason);
_logger.LogError(ex, "Failed to process order {OrderId}", orderId);

// WRONG — interpolated string loses structure, all you get is text
_logger.LogInformation($"Order {order.Id} created for customer {order.CustomerId}");
```

Naming convention for template properties: PascalCase, noun-first (`OrderId`, not `id`).

## LogContext Enrichment

Push properties onto the log context to include them in every log line emitted within a scope. Useful for request-scoped IDs.

```csharp
using (LogContext.PushProperty("UserId", userId))
using (LogContext.PushProperty("RequestId", httpContext.TraceIdentifier))
{
    await ProcessOrderAsync(orderId, ct);
}
```

In middleware, enrich once per request:

```csharp
public class UserContextMiddleware(RequestDelegate next)
{
    public async Task InvokeAsync(HttpContext context)
    {
        var userId = context.User.FindFirstValue("sub");
        if (userId is not null)
            using (LogContext.PushProperty("UserId", userId))
            {
                await next(context);
                return;
            }
        await next(context);
    }
}
```

## Request Logging Middleware

Replace ASP.NET Core's default request logging with Serilog's, which emits one structured line per request:

```csharp
// In Program.cs — place before UseRouting/UseAuthentication
app.UseSerilogRequestLogging(options =>
{
    options.MessageTemplate =
        "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";
    options.GetLevel = (ctx, elapsed, ex) =>
        ex is not null || ctx.Response.StatusCode >= 500
            ? LogEventLevel.Error
            : LogEventLevel.Information;
    options.EnrichDiagnosticContext = (diagCtx, httpContext) =>
    {
        diagCtx.Set("UserId", httpContext.User.FindFirstValue("sub"));
        diagCtx.Set("ClientIp", httpContext.Connection.RemoteIpAddress?.ToString());
    };
});
```

## Log Level Guidelines

| Level | When to use | Production default |
|-------|-------------|-------------------|
| `Verbose` / `Trace` | SQL queries, cache hits, per-frame loops | Off |
| `Debug` | Detailed flow for diagnosing a specific issue | Off |
| `Information` | Business events (order created, user logged in, job started) | On |
| `Warning` | Recoverable issues: retry attempt, cache miss storm, deprecated API call; request validation failures (a client error, not a fault) | On |
| `Error` | Failures affecting one request that the service caused or could not absorb: DB timeout, unhandled exception, downstream outage | On |
| `Fatal` | Application cannot continue: startup failure, unrecoverable state | On |

## Performance Guard

Avoid building expensive log messages that may not be written. Use the `IsEnabled` guard:

```csharp
if (_logger.IsEnabled(LogLevel.Debug))
{
    var payload = JsonSerializer.Serialize(largeObject);
    _logger.LogDebug("Full request payload: {Payload}", payload);
}
```

For hot paths, use the `[LoggerMessage]` source generator — it emits the `IsEnabled` check and avoids
boxing. It takes MEL's `LogLevel`, not Serilog's `LogEventLevel`:

```csharp
internal static partial class OrderLog
{
    [LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "Order {OrderId} created.")]
    public static partial void OrderCreated(ILogger logger, Guid orderId);
}

OrderLog.OrderCreated(_logger, order.Id);
```

## Security Rules

Never log:
- Passwords, PINs, security questions
- Auth tokens, API keys, session IDs
- PII: email addresses, names, SSNs, credit card numbers, phone numbers
- Full request bodies containing any of the above

Redact at source — don't rely on log pipeline filtering:

```csharp
// WRONG
_logger.LogInformation("User login attempt: {Email} / {Password}", email, password);

// CORRECT
_logger.LogInformation("User login attempt for account {AccountId}", accountId);
```

## See Also

- `skills/opentelemetry/SKILL.md` — trace/span setup and log↔trace correlation (`TraceId`/`SpanId`)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.