Skip to content

Seq Structured Logging

Seq is the primary log aggregation tool for local development. It provides a rich query interface for structured (JSON) log events emitted by all Microtec backend services via Serilog.


Seq in the Development Stack

PropertyValue
Port1234
URLhttp://localhost:1234
Admin credentialsadmin / (set on first run)
RetentionLocal volume (dev only)
ProtocolHTTP/1.1

Start Seq via Docker Compose:

bash
cd dev
docker compose up -d seq

Or standalone:

bash
docker run -d \
  --name seq \
  -e ACCEPT_EULA=Y \
  -p 1234:80 \
  -v /var/seq:/data \
  datalust/seq:latest

Serilog Configuration

Serilog is configured in each service's appsettings.json and appsettings.{env}.json. The full configuration is provided to builder.Host.UseSerilog() in Program.cs.

Base Configuration (appsettings.json)

json
{
  "Serilog": {
    "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File"],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "Microsoft.EntityFrameworkCore.Database.Command": "Warning",
        "System": "Warning"
      }
    },
    "Enrich": [
      "FromLogContext",
      "WithMachineName",
      "WithProcessId",
      "WithThreadId"
    ],
    "Properties": {
      "Application": "$(serviceName)",
      "Environment": "$(ASPNETCORE_ENVIRONMENT)"
    },
    "WriteTo": [
      {
        "Name": "Console",
        "Args": {
          "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties}{NewLine}{Exception}"
        }
      },
      {
        "Name": "File",
        "Args": {
          "path": "logs/log-.txt",
          "rollingInterval": "Day",
          "retainedFileCountLimit": 7
        }
      }
    ]
  }
}

Development Override (appsettings.Development.json)

json
{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Debug",
      "Override": {
        "Microsoft.EntityFrameworkCore.Database.Command": "Information"
      }
    },
    "WriteTo": [
      {
        "Name": "Seq",
        "Args": {
          "serverUrl": "http://localhost:1234"
        }
      }
    ]
  }
}

Cloud Configuration (appsettings.stage.json, etc.)

json
{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information"
    },
    "WriteTo": [
      {
        "Name": "ApplicationInsights",
        "Args": {
          "connectionString": "#{ApplicationInsights__ConnectionString}#",
          "telemetryConverter": "Serilog.Sinks.ApplicationInsights.TelemetryConverters.TraceTelemetryConverter, Serilog.Sinks.ApplicationInsights"
        }
      }
    ]
  }
}

Log Levels

LevelSerilog ConstantWhen to Use
VerboseLog.Verbose(...)Extremely detailed, diagnostic-only traces
DebugLog.Debug(...)Internal state for debugging; dev only
InformationLog.Information(...)Normal operation events (request handled, message sent)
WarningLog.Warning(...)Unexpected but recoverable conditions
ErrorLog.Error(ex, ...)Errors that affect the current operation
FatalLog.Fatal(ex, ...)System-wide failures; service cannot continue

Correlation ID Middleware

All services include X-Correlation-ID propagation. Incoming requests carry the header; if missing, a new GUID is generated. The correlation ID is added to the Serilog LogContext for all log events in the request scope.

csharp
// CorrelationIdMiddleware.cs (in Shared.Web)
public class CorrelationIdMiddleware : IMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        var correlationId = context.Request.Headers["X-Correlation-ID"].FirstOrDefault()
                           ?? Guid.NewGuid().ToString();

        context.Response.Headers["X-Correlation-ID"] = correlationId;

        using (LogContext.PushProperty("CorrelationId", correlationId))
        using (LogContext.PushProperty("TenantId", context.GetTenantId()))
        using (LogContext.PushProperty("UserId", context.GetUserId()))
        {
            await next(context);
        }
    }
}

All log events for a request will include CorrelationId, TenantId, and UserId as structured properties.


Standard Log Events

Each service emits a set of standard structured log events:

csharp
// Request received
Log.Information("Handling {RequestName} for tenant {TenantId}", 
    request.GetType().Name, tenantId);

// Command/Query result
Log.Information("Handled {RequestName} in {Elapsed}ms", 
    request.GetType().Name, stopwatch.ElapsedMilliseconds);

// External call
Log.Debug("Calling external service {ServiceName} at {Url}", 
    serviceName, url);

// Database operation
Log.Debug("Executing query {QueryName} on {Database}", 
    queryName, databaseName);

// Error with full context
Log.Error(ex, "Failed to process {RequestName} for tenant {TenantId}. " +
    "CorrelationId: {CorrelationId}", 
    requestName, tenantId, correlationId);

Seq Query Examples

Find all errors in the last hour

sql
@Level = 'Error' and @Timestamp > Now() - 1h

Find all requests for a specific tenant

sql
TenantId = 'tenant-guid-here'
order by @Timestamp desc

Find slow requests (over 1 second)

sql
@MessageTemplate like '%Handled%' and Elapsed > 1000

Trace a specific correlation ID across all services

sql
CorrelationId = 'your-correlation-id-here'
order by @Timestamp asc

Find failed authentication attempts

sql
@MessageTemplate like '%authentication%' and @Level in ['Warning', 'Error']
order by @Timestamp desc

Count errors by service in the last 24 hours

sql
@Level = 'Error' and @Timestamp > Now() - 24h
select count(*) as ErrorCount group by Application

Log Sinks Summary

SinkPackageUsed InPurpose
ConsoleSerilog.Sinks.ConsoleAll environmentsContainer stdout
FileSerilog.Sinks.FileDev + StageLocal file rotation
SeqSerilog.Sinks.SeqDevelopmentStructured log viewer
Application InsightsSerilog.Sinks.ApplicationInsightsAll cloud envsCloud log aggregation

Seq in Production

Seq is not deployed in cloud environments (dev through production). In the cloud, logs flow to Application Insights.

For cloud environments, use Azure Monitor Log Analytics queries (KQL):

kusto
// Application Insights equivalent of Seq error query
traces
| where timestamp > ago(1h)
| where severityLevel >= 3
| extend tenantId = customDimensions["TenantId"]
| project timestamp, message, tenantId, operation_Id
| order by timestamp desc

Internal Documentation — Microtec Platform Team