Appearance
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
| Property | Value |
|---|---|
| Port | 1234 |
| URL | http://localhost:1234 |
| Admin credentials | admin / (set on first run) |
| Retention | Local volume (dev only) |
| Protocol | HTTP/1.1 |
Start Seq via Docker Compose:
bash
cd dev
docker compose up -d seqOr standalone:
bash
docker run -d \
--name seq \
-e ACCEPT_EULA=Y \
-p 1234:80 \
-v /var/seq:/data \
datalust/seq:latestSerilog 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
| Level | Serilog Constant | When to Use |
|---|---|---|
| Verbose | Log.Verbose(...) | Extremely detailed, diagnostic-only traces |
| Debug | Log.Debug(...) | Internal state for debugging; dev only |
| Information | Log.Information(...) | Normal operation events (request handled, message sent) |
| Warning | Log.Warning(...) | Unexpected but recoverable conditions |
| Error | Log.Error(ex, ...) | Errors that affect the current operation |
| Fatal | Log.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() - 1hFind all requests for a specific tenant
sql
TenantId = 'tenant-guid-here'
order by @Timestamp descFind slow requests (over 1 second)
sql
@MessageTemplate like '%Handled%' and Elapsed > 1000Trace a specific correlation ID across all services
sql
CorrelationId = 'your-correlation-id-here'
order by @Timestamp ascFind failed authentication attempts
sql
@MessageTemplate like '%authentication%' and @Level in ['Warning', 'Error']
order by @Timestamp descCount errors by service in the last 24 hours
sql
@Level = 'Error' and @Timestamp > Now() - 24h
select count(*) as ErrorCount group by ApplicationLog Sinks Summary
| Sink | Package | Used In | Purpose |
|---|---|---|---|
| Console | Serilog.Sinks.Console | All environments | Container stdout |
| File | Serilog.Sinks.File | Dev + Stage | Local file rotation |
| Seq | Serilog.Sinks.Seq | Development | Structured log viewer |
| Application Insights | Serilog.Sinks.ApplicationInsights | All cloud envs | Cloud 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