# Observability Standard: Logging

## Overview
Structured logging with Serilog, enrichers, sinks, and log level configuration.

## Serilog Setup

### Package Installation
```xml
<PackageReference Include="Serilog.AspNetCore" Version="8.*" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.*" />
<PackageReference Include="Serilog.Enrichers.Process" Version="3.*" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.*" />
<PackageReference Include="Serilog.Sinks.ApplicationInsights" Version="4.*" />
```

### Program.cs Configuration
```csharp
Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(builder.Configuration)
    .Enrich.FromLogContext()
    .Enrich.WithEnvironmentName()
    .Enrich.WithMachineName()
    .Enrich.WithProcessId()
    .Enrich.WithThreadId()
    .WriteTo.Console(outputTemplate:
        "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}")
    .WriteTo.ApplicationInsights(
        builder.Configuration["ApplicationInsights:ConnectionString"],
        TelemetryConverter.Traces)
    .CreateLogger();

builder.Host.UseSerilog();
```

### appsettings.json Level Configuration
```json
{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "Microsoft.Hosting.Lifetime": "Information",
        "System": "Warning",
        "Microsoft.EntityFrameworkCore": "Warning"
      }
    }
  }
}
```

## Structured Logging Patterns

### Correct: Structured Properties
```csharp
// ✅ Use structured logging — properties are searchable in App Insights
logger.LogInformation("User {UserId} created order {OrderId} for {Amount:C}",
    userId, orderId, amount);

// ✅ Log exceptions with context
logger.LogError(ex, "Failed to process payment for order {OrderId} (attempt {Attempt}/{MaxAttempts})",
    orderId, attempt, maxAttempts);
```

### Wrong: String Interpolation
```csharp
// ❌ Never use string interpolation — loses structured properties
logger.LogInformation($"User {userId} created order {orderId}");

// ❌ Never concatenate
logger.LogError("Failed for order " + orderId);
```

## Log Scopes
```csharp
// Add correlation context for a request scope
using (logger.BeginScope(new Dictionary<string, object>
{
    ["CorrelationId"] = correlationId,
    ["UserId"] = userId,
    ["Feature"] = "checkout"
}))
{
    // All logs within this scope include the properties above
    await ProcessCheckoutAsync(cart);
}
```

## Log Categories — What to Log

| Level | When |
|-------|------|
| `Trace` | Never in production (dev only) |
| `Debug` | Detailed flow, timing — disabled in prod |
| `Information` | Business events: order created, user signed in, job started |
| `Warning` | Degraded state: retry, rate limit, cache miss cascade |
| `Error` | Handled exceptions: payment failed, validation error |
| `Critical` | System failures: database unavailable, startup failure |

## Sensitive Data — NEVER Log
- Passwords or hashed passwords
- Credit card numbers or CVV
- Social security numbers
- JWT tokens or API keys
- Full connection strings
- PII beyond user ID (no email, phone, address)

## Performance Logging Pattern
```csharp
public class PerformanceLoggingBehavior<TRequest, TResponse>
    : IPipelineBehavior<TRequest, TResponse>
{
    private readonly ILogger<PerformanceLoggingBehavior<TRequest, TResponse>> _logger;

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
    {
        var sw = Stopwatch.StartNew();
        var response = await next();
        sw.Stop();

        if (sw.ElapsedMilliseconds > 500)
        {
            _logger.LogWarning("Slow request {RequestName} took {ElapsedMs}ms",
                typeof(TRequest).Name, sw.ElapsedMilliseconds);
        }

        return response;
    }
}
```
