CodeGym /행동 /C# SELF /구조화된 로깅과 Serilog

구조화된 로깅과 Serilog

C# SELF
레벨 64 , 레슨 1
사용 가능

1. 소개

보통의 텍스트 로깅이 너의 메모장에 남긴 한 줄짜리 일기 "오늘 밥 먹음"이라면, 구조화된 로깅은 각 로그 항목을 필드가 있는 카드로 바꿔줘: {날짜: ..., 이벤트: "먹음", 칼로리: 500, 메뉴: "구운 고기"}. 이 말은 나중에 일기를 단순히 읽는 것뿐만 아니라, 한 달 칼로리 그래프를 그리거나, 메뉴별로 필터링하고, 너무 늦게 먹었는지 확인할 수 있다는 뜻이야.

왜 단순 텍스트만으로는 부족할까?

단순 텍스트는 처음엔 편하지만 곧 한계가 와. 에러 통계를 로그에서 모으거나, 온라인 샵의 매출량 집계, 특정 사용자 ID(예: 42)로 행동 시퀀스를 추적하려고 할 때, 모든 데이터가 단순한 텍스트 덩어리라면 골치 아파. 구조화된 로깅은 로그에 분석이나 심지어 AI를 붙일 수 있게 해줘. 이상 징후를 찾고, 대시보드를 만들고, 문제에 자동으로 반응하게 할 수 있어.

장점

  • 메시지뿐만 아니라 관련 데이터(필드/속성)를 같이 로깅할 수 있어.
  • 로그를 자동으로 분석할 수 있어: 집계, 필터링, 리포트 생성 등.
  • JSON 같은 표준 포맷을 사용하면 기계가 파싱하기 쉬워.

Serilog: 이게 뭐고 왜 쓰지?

Serilog (공식 사이트: serilog.net, 문서: github.com/serilog/serilog/wiki)는 .NET용 구조화된 로깅 라이브러리야. SerilogMicrosoft.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");

이건 단순히 보기 좋은 문법이 아니라 구조화된 로깅이야: 레코드에 OperationFileName 필드가 들어가서 필터링과 집계가 가능해.

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 레벨을 남발하지 마—InformationWarning을 적절히 사용해.
  • 문자열 연결 대신 이름 있는 매개변수를 템플릿에 사용해.
  • 민감한 데이터(비밀번호, 토큰, 키)는 절대 로깅하지 마.
  • 오류·예외뿐 아니라 중요한 비즈니스 이벤트도 로깅해.
  • 로그 파일의 롤링과 정리 정책을 설정해서 디스크가 꽉 차지 않게 해.

시각화 및 분석: Seq, Kibana, Application Insights

Serilog는 다양한 sink를 지원해서 로그를 여러 목적지로 보낼 수 있어.

Sink 간단한 설명 주로 사용되는 곳
Console 콘솔로 바로 출력 개발, 테스트
File 로컬 또는 네트워크 파일에 저장 소규모 프로젝트, dev 환경
Seq 필터링과 대시보드가 있는 웹 인터페이스 엔터프라이즈, 분석
ElasticSearch 강력한 저장 및 분석 시스템 대규모 조직
Azure Application Insights 클라우드 모니터링과 텔레메트리 Azure 기반 서비스

Seq (datalust.co/seq)는 내부 사용과 개발에 아주 편한 솔루션이야: 필드 기반 검색, 빠른 배포, 대시보드 등 편의 기능이 많아.

표와 시각화

아래는 구조화해서 로깅하기 좋은 항목들의 간단한 표야:

로깅 대상 Serilog에서의 표현 예시 값
사용자 ID
{UserId}
123
동작
{Action}
"삭제"
오류
Log.Error(ex, "모듈 {Module}에서 오류 발생")
"등록 모듈"
작업 시간 {Elapsed:0.000} 1.234
파일 이름
{FileName}
"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>는 로깅 구현체에 독립적이야 — 필요하면 NLogLog4Net으로 바꾸면 돼.

7. 흔한 실수들

로그 과다: 모든 걸 다 찍어버리면 진짜 중요한 정보를 찾기 어려워.

예외를 텍스트로만 로깅: 예외 객체를 같이 넘겨줘야 오류 구조가 로그에 남아 더 분석하기 쉬워.

try
{
    // some code
}
catch (Exception ex)
{
    Log.Error(ex, "요청 처리 중 오류가 발생했습니다");
}

설정 남발: 구성 파일을 지저분하게 만들지 말고 필요한 sink와 레벨만 추가해.

2
과제
C# SELF, 레벨 64, 레슨 1
잠금
Serilog을 이용한 기본 구조화 로깅
Serilog을 이용한 기본 구조화 로깅
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION