이야기를 디버깅하는 개발자.

개발과 창작 사이에서, 사람들이 자기만의 이야기를 만들 수 있는 도구와 기록을 만듭니다.

roslyn.dev 자세히보기

개발 노트/ASP.NET Core

닷넷 프로젝트에서 Serilog를 자주 쓰는 이유

Roslyn 2026. 7. 6. 20:00
반응형

닷넷 프로젝트에서 Serilog를 자주 쓰는 이유
닷넷 프로젝트를 만들다 보면 로깅 도구를 고르는 순간이 자주 옵니다. 작은 기능을 만들 때는 Console.WriteLine만으로도 충분해 보이지만, 서비스가 실제로 운영되기 시작하면 이야기가 달라집니다. 문제가 생겼을 때 “어디에서”, “어떤 값으로”, “누가”, “얼마나 자주” 발생했는지 확인할 수 있어야 하기 때문입니다.

저는 닷넷으로 서비스를 만들고 운영하면서 Serilog를 자주 사용합니다. 대단히 특별한 이유라기보다, 혼자서도 상황을 추적하고 정리하기에 현실적인 도구라고 느껴왔기 때문입니다. 로그는 개발 중 확인용 메시지가 아니라, 운영 중인 서비스를 이해하기 위한 기록에 가깝습니다. 그런 관점에서 Serilog는 꽤 오랫동안 손에 익은 선택지였습니다.

문자열 로그보다 구조화된 기록이 필요했습니다
일반적인 로그는 사람이 읽기 좋은 문장으로 남습니다.

사용자 로그인 실패: userId=1234, reason=password_mismatch
이런 로그도 당장은 충분해 보입니다. 하지만 운영 중 비슷한 로그가 수천 줄씩 쌓이면, 단순 문자열 검색만으로 원인을 찾기 어렵습니다. 특정 사용자만 확인하고 싶을 수도 있고, 실패 사유별로 건수를 보고 싶을 수도 있습니다. 요청 경로, 응답 시간, 예외 정보, 사용자 식별자 같은 값을 따로 묶어서 보고 싶을 때도 있습니다.

Serilog의 장점은 이 지점에서 드러납니다. Serilog는 메시지 안에 들어간 값을 단순 문자열로 합쳐버리지 않고, 이름이 붙은 속성으로 함께 기록할 수 있습니다.

Log.Information("Login failed for {UserId} because {Reason}", userId, reason);
이렇게 남기면 로그 문장은 사람이 읽을 수 있고, 동시에 UserId와 Reason은 검색하거나 필터링할 수 있는 값으로 남습니다. 나중에 Seq, Elasticsearch, Application Insights 같은 도구와 연결했을 때 이 차이가 꽤 크게 느껴집니다.

Serilog를 쓰면 로그의 목적지가 유연해집니다
Serilog에서 로그를 어디로 보낼지는 Sink라는 개념으로 정리합니다. 콘솔에 출력할 수도 있고, 파일로 남길 수도 있고, Seq나 Elasticsearch 같은 로그 수집 도구로 보낼 수도 있습니다.

개발 중에는 콘솔 로그가 편합니다. 로컬에서 바로 확인할 수 있기 때문입니다. 반면 운영 환경에서는 파일이나 별도의 로그 서버에 남겨야 할 때가 많습니다. 중요한 것은 이 구성이 코드 곳곳에 흩어지지 않는다는 점입니다. 보통은 appsettings.json 같은 설정 파일에 정리해두고, 환경에 따라 다르게 적용합니다.

{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    },
    "Enrich": [ "FromLogContext" ],
    "WriteTo": [
      { "Name": "Console" },
      {
        "Name": "File",
        "Args": {
          "path": "logs/app-.log",
          "rollingInterval": "Day"
        }
      }
    ]
  }
}
이런 방식은 혼자 서비스를 운영할 때 특히 편합니다. 개발 환경에서는 자세히 보고, 운영 환경에서는 필요한 수준만 남기도록 조정할 수 있습니다. 로그 설정을 바꾸기 위해 매번 코드를 수정하는 부담도 줄어듭니다.

Enrich는 운영 중 맥락을 남기는 데 도움이 됩니다
서비스 장애를 확인할 때 가장 답답한 순간은 로그 한 줄만으로는 상황을 알 수 없을 때입니다. 예외 메시지는 남아 있지만 어떤 요청에서 발생했는지 모를 때가 있습니다. 사용자는 누구였는지, 어떤 화면이나 API를 거쳤는지, 어떤 서버에서 실행됐는지 알 수 없으면 원인을 좁히는 데 시간이 오래 걸립니다.

Serilog의 Enrich 기능은 이런 맥락을 로그에 덧붙이는 역할을 합니다. 예를 들어 요청 ID, 사용자 ID, 머신 이름, 스레드 ID 같은 정보를 함께 남길 수 있습니다.

using (LogContext.PushProperty("UserId", userId))
{
    Log.Information("User requested novel draft list");
}
이렇게 남긴 값은 해당 흐름 안에서 발생하는 로그에 함께 붙습니다. 나중에 특정 사용자나 특정 요청을 기준으로 로그를 묶어볼 수 있습니다. 단순히 “오류가 났다”가 아니라 “어떤 맥락에서 오류가 났는지”를 볼 수 있는 것입니다.

ASP.NET Core에서는 요청 로그 정리가 편합니다
ASP.NET Core 프로젝트에서는 Serilog.AspNetCore를 함께 사용하는 경우가 많습니다. 기본 요청 로그를 Serilog의 흐름으로 정리할 수 있고, UseSerilogRequestLogging()을 통해 HTTP 요청 단위의 로그를 남길 수 있습니다.

builder.Host.UseSerilog((context, services, configuration) => configuration
    .ReadFrom.Configuration(context.Configuration)
    .ReadFrom.Services(services)
    .Enrich.FromLogContext());

app.UseSerilogRequestLogging();
요청 로그가 정리되면 운영 중 확인해야 할 기본 정보가 훨씬 안정적으로 남습니다. 어떤 API가 느린지, 어떤 경로에서 오류가 자주 나는지, 특정 시간대에 요청이 몰리는지 살펴보기 쉬워집니다. 큰 시스템이 아니더라도 이런 기록은 나중에 서비스를 다듬는 기준이 됩니다.

제가 Serilog를 사용할 때 신경 쓰는 기준
Serilog를 쓴다고 해서 로그가 자동으로 좋아지는 것은 아닙니다. 오히려 아무 기준 없이 많이 남기면 나중에 더 찾기 어려운 기록이 됩니다. 그래서 저는 몇 가지 기준을 두고 사용하려고 합니다.

첫째, 문자열 보간보다 메시지 템플릿을 사용합니다.

// 피하고 싶은 방식
Log.Information($"Order {orderId} completed in {elapsedMs}ms");

// 선호하는 방식
Log.Information("Order {OrderId} completed in {ElapsedMs}ms", orderId, elapsedMs);
두 번째 방식은 OrderId와 ElapsedMs가 속성으로 남습니다. 나중에 검색과 집계가 쉬워집니다.

둘째, 로그 레벨을 너무 가볍게 쓰지 않습니다. Information은 서비스 흐름을 이해하는 데 필요한 기록에 사용하고, Warning은 당장 실패는 아니지만 확인이 필요한 상황에 사용합니다. Error는 실제로 처리가 실패했거나 사용자가 영향을 받을 수 있는 경우에 남기는 편이 좋습니다.

셋째, 민감한 정보는 로그에 남기지 않습니다. 이메일, 전화번호, 토큰, 결제 정보, 비밀번호 같은 값은 운영 편의를 위해 남기기 시작하면 나중에 더 큰 위험이 됩니다. 로그는 문제 해결을 돕는 기록이어야지, 개인정보 저장소가 되어서는 안 됩니다.

넷째, 운영자가 실제로 볼 수 있는 위치에 로그를 둡니다. 아무리 좋은 로그를 남겨도 확인하기 어렵다면 의미가 줄어듭니다. 작은 서비스라면 파일 로그와 콘솔만으로도 시작할 수 있습니다. 규모가 커지거나 추적이 어려워지면 Seq 같은 도구를 붙여 구조화된 검색 환경을 만드는 식으로 단계적으로 늘리는 편이 현실적입니다.

작은 서비스일수록 로그가 더 중요할 때가 있습니다
큰 조직에서는 모니터링, 알림, 장애 대응 체계가 따로 마련되어 있는 경우가 많습니다. 하지만 혼자 만드는 서비스에서는 그런 체계를 처음부터 모두 갖추기 어렵습니다. 그렇다고 문제가 생겼을 때 감으로만 대응할 수는 없습니다.

저는 작은 서비스일수록 최소한의 로그 기준이 필요하다고 느낍니다. 사용자가 어떤 흐름에서 막혔는지, 특정 기능이 실제로 호출되고 있는지, 예외가 반복되는 구간은 어디인지 확인할 수 있어야 합니다. Serilog는 이 기준을 너무 무겁지 않게 시작할 수 있게 도와줍니다.

특히 메리톡톡처럼 창작 흐름을 돕는 서비스를 만들 때는 사용자의 작업 흐름을 방해하지 않는 안정성이 중요합니다. 기능이 많아지는 것보다, 문제가 생겼을 때 원인을 찾아 다시 이어갈 수 있는 구조가 먼저일 때가 있습니다. 로그는 그 구조를 받쳐주는 가장 기본적인 기록입니다.

마무리
Serilog는 닷넷에서 많이 쓰이는 로깅 라이브러리입니다. 하지만 제게는 단순히 유명해서 쓰는 도구라기보다, 서비스를 오래 운영하기 위해 필요한 기록 방식을 정리해주는 도구에 가깝습니다.

로그는 개발자의 습관처럼 보이지만, 실제로는 운영자의 태도와도 연결됩니다. 문제가 생겼을 때 원인을 찾을 수 있게 남기는 것, 나중에 다시 읽어도 상황을 이해할 수 있게 정리하는 것, 필요한 정보와 남기면 안 되는 정보를 구분하는 것. 이런 작은 기준들이 쌓이면 서비스는 조금 더 오래 버틸 수 있습니다.

아직 모든 프로젝트에 완벽한 로깅 구조를 갖추고 있는 것은 아닙니다. 다만 닷넷 프로젝트를 다시 만들 때마다 Serilog를 먼저 떠올리는 이유는 분명합니다. 혼자 만들고 운영하는 입장에서, 나중의 저를 도와주는 기록을 남기기에 꽤 현실적인 도구이기 때문입니다.

반응형