Microsoft .NET/ASP.Net & ASP.Net Core
[ASP.NET Core] Serilog 구조화 로깅(Structured Logging)과 Seq 연동 실무 세팅
전자기린
2026. 11. 11. 12:10

ASP.NET Core에 Serilog를 붙여 구조화 로깅을 구성하고, Seq로 로그를 모아 조회하는 실무 세팅입니다. 콘솔에는 JSON, Seq에는 구조화 이벤트로 보내고, 요청 로그를 한 줄로 요약하는 구성까지 정리합니다.
절차
- 패키지 설치
- Seq 기동
- Program.cs 구성
- appsettings.json 설정
- 요청 로깅과 상관관계 ID
- 구조화 로그 작성 규칙
- 조회와 운영 팁
1. 패키지 설치
dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Sinks.Seq
dotnet add package Serilog.Formatting.Compact
dotnet add package Serilog.Enrichers.Environment
Serilog.AspNetCore는 콘솔 싱크와 설정 바인딩(Serilog.Settings.Configuration), 호스팅 통합을 함께 가져옵니다.
2. Seq 기동
docker run -d --name seq \
-e ACCEPT_EULA=Y \
-e SEQ_FIRSTRUN_ADMINPASSWORD="ChangeMe123!" \
-p 5341:80 \
-v $(pwd)/seq-data:/data \
datalust/seq
- 5341: 수집(ingest)과 UI 포트. 애플리케이션은 이 포트로 로그를 보냅니다.
- 관리 UI는
http://localhost:5341에서 바로 열립니다. - 운영에서는 API 키를 만들고 수집 요청에만 쓰는 키를 분리합니다.
3. Program.cs 구성
using Serilog;
using Serilog.Formatting.Compact;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog((context, services, configuration) => configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
.Enrich.WithProperty("Application", "MyApi")
.WriteTo.Console(new CompactJsonFormatter())
.WriteTo.Seq(
serverUrl: context.Configuration["Seq:ServerUrl"] ?? "http://localhost:5341",
apiKey: context.Configuration["Seq:ApiKey"]));
var app = builder.Build();
app.UseSerilogRequestLogging(options =>
{
options.MessageTemplate =
"{RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";
});
app.MapGet("/health", () => Results.Ok(new { status = "ok" }));
app.Run();
UseSerilog로 호스팅 로그를 Serilog로 넘깁니다.ReadFrom.Services는 DI에 등록된 enricher와 sink를 반영합니다.UseSerilogRequestLogging을 파이프라인 앞쪽에 두면 요청당 한 줄로 정리된 로그가 남습니다.- 기본 요청 로깅은
Information레벨이고, 정적 파일·헬스 체크는GetLevel로 걸러냅니다.
options.GetLevel = (httpContext, elapsed, ex) =>
httpContext.Request.Path.StartsWithSegments("/health")
? LogEventLevel.Debug
: LogEventLevel.Information;
4. appsettings.json
{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning"
}
},
"Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"],
"WriteTo": [
{ "Name": "Console" },
{
"Name": "Seq",
"Args": { "serverUrl": "http://localhost:5341" }
}
]
},
"Seq": {
"ServerUrl": "http://localhost:5341",
"ApiKey": ""
}
}
Override로 프레임워크 로그를 조용히 시킵니다. 이 설정이 없으면 요청 한 건에 수십 줄이 붙습니다.- EF Core 명령 로그는
Information에서 SQL 전체를 남기므로 운영에서는Warning이상으로 올립니다. Enrich.WithMachineName은 컨테이너 환경에서 인스턴스 구분에 필요합니다. 호스트명이 매번 바뀌는 오케스트레이션에서는 파드 이름이 그대로 찍힙니다.
5. 요청 로깅과 상관관계 ID
여러 서비스를 거치는 요청은 상관관계 ID로 묶어야 추적이 됩니다.
app.Use(async (context, next) =>
{
var traceId = context.Request.Headers["X-Trace-Id"].FirstOrDefault()
?? context.TraceIdentifier;
using (LogContext.PushProperty("TraceId", traceId))
{
context.Response.Headers["X-Trace-Id"] = traceId;
await next();
}
});
app.MapGet("/orders/{id:int}", async (int id, ILogger<Program> logger) =>
{
logger.LogInformation("주문 조회 {OrderId} by {UserId}", id, 42);
return Results.Ok();
});
LogContext.PushProperty는 using 범위 안에서만 유효합니다. 미들웨어에서 await next()를 감싸면 그 요청의 모든 로그에 TraceId가 붙습니다.
6. 구조화 로그 작성 규칙
- 문자열 보간 대신 메시지 템플릿을 씁니다. 보간(
$"...")은 값을 문자열로 굳혀 검색할 수 없게 만듭니다. - 속성 이름은 파스칼 표기로 통일합니다(
OrderId,UserId). Seq에서 필드로 잡힙니다. - 객체 전체를 남길 때는
{@Order}로 구조 분해합니다. - 예외는 두 번째 인자로 넘깁니다. 문자열에 넣으면 스택 트레이스가 사라집니다.
// 권장
logger.LogInformation("결제 승인 {OrderId} {Amount} {Currency}", orderId, amount, "KRW");
logger.LogWarning(ex, "결제 실패 {OrderId}", orderId);
logger.LogInformation("주문 원본 {@Order}", order);
// 피해야 할 형태
logger.LogInformation($"결제 승인 {orderId} {amount} KRW");
7. 조회와 운영 팁
Seq 쿼리는 필드명으로 바로 씁니다.
@Level = 'Error'
Application = 'MyApi' and RequestPath like '/api/orders/%'
TraceId = '0HN7...'
Elapsed > 1000
- 로그 레벨은 환경별로 다르게 둡니다. 개발은
Debug, 운영은Information이 기본입니다. - 민감 정보는 구조화 로깅에서 더 위험합니다. 토큰·주민번호·비밀번호가 속성으로 잡히면 검색과 보관이 쉬워지기 때문입니다. 마스킹 정책을 먼저 정합니다.
- 보관 기간은 Seq의 retention 정책으로 관리합니다. 무제한 보관은 디스크를 채웁니다.
- 수집 실패가 애플리케이션을 멈추지 않도록 Seq 싱크는 비동기로 동작합니다. 네트워크 단절 시 로그가 유실될 수 있으므로, 감사 목적 로그는 별도 저장소에 함께 남깁니다.
- 로그 상관관계를 위해 응답 헤더에
X-Trace-Id를 돌려주면 장애 문의 때 바로 추적할 수 있습니다.
정리
Serilog.AspNetCore와 Serilog.Sinks.Seq를 설치하고 UseSerilog로 호스팅 로그를 넘깁니다. 콘솔은 JSON, Seq는 구조화 이벤트로 보내고, 요청 로깅과 TraceId 푸시를 붙이면 요청 단위 추적이 완성됩니다. 메시지 템플릿 규칙만 지켜도 검색 가능한 로그가 됩니다.