1. 소개
보통의 텍스트 로깅이 너의 메모장에 남긴 한 줄짜리 일기 "오늘 밥 먹음"이라면, 구조화된 로깅은 각 로그 항목을 필드가 있는 카드로 바꿔줘: {날짜: ..., 이벤트: "먹음", 칼로리: 500, 메뉴: "구운 고기"}. 이 말은 나중에 일기를 단순히 읽는 것뿐만 아니라, 한 달 칼로리 그래프를 그리거나, 메뉴별로 필터링하고, 너무 늦게 먹었는지 확인할 수 있다는 뜻이야.
왜 단순 텍스트만으로는 부족할까?
단순 텍스트는 처음엔 편하지만 곧 한계가 와. 에러 통계를 로그에서 모으거나, 온라인 샵의 매출량 집계, 특정 사용자 ID(예: 42)로 행동 시퀀스를 추적하려고 할 때, 모든 데이터가 단순한 텍스트 덩어리라면 골치 아파. 구조화된 로깅은 로그에 분석이나 심지어 AI를 붙일 수 있게 해줘. 이상 징후를 찾고, 대시보드를 만들고, 문제에 자동으로 반응하게 할 수 있어.
장점
- 메시지뿐만 아니라 관련 데이터(필드/속성)를 같이 로깅할 수 있어.
- 로그를 자동으로 분석할 수 있어: 집계, 필터링, 리포트 생성 등.
- JSON 같은 표준 포맷을 사용하면 기계가 파싱하기 쉬워.
Serilog: 이게 뭐고 왜 쓰지?
Serilog (공식 사이트: serilog.net, 문서: github.com/serilog/serilog/wiki)는 .NET용 구조화된 로깅 라이브러리야. Serilog는 Microsoft.Extensions.Logging와 잘 통합되고, 파일·콘솔·Seq·ElasticSearch·Grafana·Azure 등 수십 가지 시스템으로 출력할 수 있으며, 성능 영향이 적고 설정도 쉽지.
Serilog은 단순 로깅과 뭐가 다를까?
- 구조: 로그가 필드가 있는 객체라 필드로 필터링할 수 있어(예: ID가 42인 사용자 관련 모든 에러).
- 포맷: 텍스트뿐 아니라 JSON, XML 등으로도 쓸 수 있어서 후처리가 편리해.
- 유연성: 다양한 준비된 sink 패키지가 있어서 로그를 원하는 곳으로 보낼 수 있어.
Serilog 로그 항목의 구조
코드 쓰기 전에 구조화된 로그가 어떻게 생겼는지 보자.
{
"Timestamp": "2024-06-22T10:23:45.123Z",
"Level": "Information",
"MessageTemplate": "사용자 {UserId}가 시스템에 로그인함",
"Properties": {
"UserId": 42,
"IpAddress": "127.0.0.1"
}
}
여기서도 쉽게 알 수 있지: 사용자 번호는 42고, IP는 해당 주소라는 걸.
2. C# 프로젝트에서 Serilog 설치 및 기본 설정
1단계. NuGet 패키지 설치
Rider/Visual Studio에서 NuGet Package Manager로 다음을 설치해:
- Serilog
- Serilog.Sinks.Console (콘솔 출력)
- Serilog.Extensions.Logging (Microsoft.Extensions.Logging와 통합하려고)
커맨드라인으로는:
dotnet add package Serilog
dotnet add package Serilog.Sinks.Console
2단계. 최소 설정
Program.cs에 설정을 넣고 첫 로그를 남겨보자.
using System;
using Serilog;
namespace MySuperApp
{
class Program
{
static void Main(string[] args)
{
// 1. Serilog 기본 설정: 콘솔 출력
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.CreateLogger();
// 2. 구조화된 로깅 예시
int userId = 42;
string ip = "127.0.0.1";
Log.Information("사용자 {UserId}가 IP {IpAddress}로 시스템에 로그인함", userId, ip);
Log.CloseAndFlush();
}
}
}
콘솔에는 대략 이렇게 보일 거야:
[10:30:16 INF] 사용자 42가 IP 127.0.0.1로 시스템에 로그인함
이제 출력은 JSON-파일, Seq 등으로도 쉽게 보낼 수 있어.
3. 로그 포맷: Message Template
Serilog에서는 문자열 연결 대신 템플릿 문법을 써:
Log.Information("작업 {Operation}이 파일 {FileName}에서 수행됨", "삭제", "test.txt");
이건 단순히 보기 좋은 문법이 아니라 구조화된 로깅이야: 레코드에 Operation과 FileName 필드가 들어가서 필터링과 집계가 가능해.
string.Format과 어떤 차이?
string.Format("작업 {0}이 파일 {1}에 대해", operation, fileName)는 단순히 플레이스홀더로 문자열을 합칠 뿐이야. 반면 Serilog는 별도의 필드를 만들어 나중에 분석할 때 쓸 수 있게 해줘.
유연한 설정: 레벨, 필터, 여러 «sinks»
Serilog는 로그를 동시에 여러 곳에 쓸 수 있어.
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.File("log.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
이제 로그는 콘솔에도, 일별 롤링되는 파일에도 쓰여.
4. 예제
간단한 콘솔형 '메모장' 앱을 만든다고 해보자. 사용자가 메모를 만드는 행동을 구조화해서 로깅하자.
using System;
using Serilog;
namespace NotesApp
{
class Program
{
static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.Console()
.WriteTo.File("notes-log.json", rollingInterval: RollingInterval.Day,
formatter: new Serilog.Formatting.Json.JsonFormatter())
.CreateLogger();
Console.WriteLine("사용자 이름을 입력하세요:");
string userName = Console.ReadLine();
Log.Information("사용자 {UserName}가 NotesApp을 실행함", userName);
while (true)
{
Console.WriteLine("메모 내용을 입력하세요 (또는 '종료'라고 입력):");
string note = Console.ReadLine();
if (note == "종료")
{
Log.Information("사용자 {UserName}가 작업을 종료함", userName);
break;
}
Log.Information("사용자 {UserName}가 메모를 생성함: {NoteText}", userName, note);
}
Log.CloseAndFlush();
}
}
}
코멘트:
로그는 누가 프로그램을 실행했는지, 어떤 입력을 했는지, 언제 종료했는지를 기록해. notes-log.json 파일에서는 각 항목이 분석하기 쉬운 JSON 객체로 저장돼.
5. 유용한 팁
구조화된 로깅 모범 사례
- 프로덕션에서는 Debug/Trace 레벨을 남발하지 마—Information과 Warning을 적절히 사용해.
- 문자열 연결 대신 이름 있는 매개변수를 템플릿에 사용해.
- 민감한 데이터(비밀번호, 토큰, 키)는 절대 로깅하지 마.
- 오류·예외뿐 아니라 중요한 비즈니스 이벤트도 로깅해.
- 로그 파일의 롤링과 정리 정책을 설정해서 디스크가 꽉 차지 않게 해.
시각화 및 분석: Seq, Kibana, Application Insights
Serilog는 다양한 sink를 지원해서 로그를 여러 목적지로 보낼 수 있어.
| Sink | 간단한 설명 | 주로 사용되는 곳 |
|---|---|---|
| Console | 콘솔로 바로 출력 | 개발, 테스트 |
| File | 로컬 또는 네트워크 파일에 저장 | 소규모 프로젝트, dev 환경 |
| Seq | 필터링과 대시보드가 있는 웹 인터페이스 | 엔터프라이즈, 분석 |
| ElasticSearch | 강력한 저장 및 분석 시스템 | 대규모 조직 |
| Azure Application Insights | 클라우드 모니터링과 텔레메트리 | Azure 기반 서비스 |
Seq (datalust.co/seq)는 내부 사용과 개발에 아주 편한 솔루션이야: 필드 기반 검색, 빠른 배포, 대시보드 등 편의 기능이 많아.
표와 시각화
아래는 구조화해서 로깅하기 좋은 항목들의 간단한 표야:
| 로깅 대상 | Serilog에서의 표현 | 예시 값 |
|---|---|---|
| 사용자 ID | |
123 |
| 동작 | |
"삭제" |
| 오류 | |
"등록 모듈" |
| 작업 시간 | {Elapsed:0.000} 초 | 1.234 |
| 파일 이름 | |
"report.pdf" |
흥미로운 팁 및 추가 기능 Serilog
- Enrichers: 각 로그에 공통 속성을 추가해줘 (예: .Enrich.WithMachineName()).
- 연결된 로그: RequestId 같은 값을 추가해서 이벤트 체인을 연관시켜라.
- appsettings.json을 통한 구성: 프로덕션 환경에서 관리하기 편해.
{
"Serilog": {
"MinimumLevel": "Debug",
"WriteTo": [
{ "Name": "Console" },
{ "Name": "File", "Args": { "path": "log.txt" } }
]
}
}
고급 sink: Slack, Telegram, 이메일로 로그를 보낼 수도 있어 — 다만 에러 하나당 메일 천통 오면 골치니 신중히 설정해.
6. 실습: Microsoft.Extensions.Logging와 통합
.NET에서는 특정 라이브러리에 묶이지 않으려고 표준 인터페이스 ILogger를 자주 사용해. Serilog를 프로바이더로 연결할 수 있어.
1단계. 패키지 설치
dotnet add package Serilog.Extensions.Logging
2단계. 구성
using Microsoft.Extensions.Logging;
using Serilog;
// ...
// 보통처럼 Serilog 구성:
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
// 이제 Microsoft.Extensions.Logging 사용
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddSerilog();
});
ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
logger.LogInformation("테스트 메시지: {TestValue}", 123);
// 닫는 것 잊지 말기:
Log.CloseAndFlush();
코멘트:
이제 코드에서 사용하는 ILogger<T>는 로깅 구현체에 독립적이야 — 필요하면 NLog나 Log4Net으로 바꾸면 돼.
7. 흔한 실수들
로그 과다: 모든 걸 다 찍어버리면 진짜 중요한 정보를 찾기 어려워.
예외를 텍스트로만 로깅: 예외 객체를 같이 넘겨줘야 오류 구조가 로그에 남아 더 분석하기 쉬워.
try
{
// some code
}
catch (Exception ex)
{
Log.Error(ex, "요청 처리 중 오류가 발생했습니다");
}
설정 남발: 구성 파일을 지저분하게 만들지 말고 필요한 sink와 레벨만 추가해.
GO TO FULL VERSION