포스트

C# Serilog 사용법

C# Serilog 사용법

로깅은 “나중에 문제 생겼을 때, 과거에 무슨 일이 있었는지”를 재현하기 위해 남겨두는 블랙박스 같은 역할을 합니다. 그 중에서도 Serilog 는 구조화 로그(Structured Logging)를 지원해서, 단순 문자열 로그를 넘어서 검색/분석 하기 좋은 로그를 남길 수 있게 해줍니다. 이글 에서는:

  1. Serilog 가 뭐고, 왜 쓰는지
  2. Nuget 설치
  3. 가장 단순한 콘솔 예제
  4. **파일 로그, 로그 레벨, 롤링 파일
  5. WPF 앱에서 Serilog 초기화 & 전역 예외 처리
  6. 구조화 로그(파라미터)와 Tip 까지 한 번에 정리해볼게요.

1. Serilog 는 뭐가 좋은가?

간단히 정리하면:

  • 설정이 쉽다: 코드 몇 줄로 바로 로그 파일 생성 가능
  • Structured Logging: UserId=123, OrderId=456 같은 필드를 따로 남겨서, 나중에 “UserId=123 인 로그 만 검색” 이런 게 쉬움
  • Sinks 가 많다: 파일, 콘솔, Seq, ElasticSearch, DB 등등 다양
  • 레벨별 필터링: 개발/운영 환경에 따라 Debug를 끄거나, Error만 남기기를 쉽게 설정 가능

2. Nuget 패키지 설치

기본적으로 아래 정도만 설치해도 웬만하면 다 됩니다.

  • Serilog
  • Serilog.Sinks.File (파일 로그)
  • Serilog.Sinks.Console (콘솔)
  • Serilog.Sinks.Async (비동기 기록 - UI 끊김 방지)
  • (선택) Serilog.Exceptions (예외 정보 디테일하게 남기기)
  • (선택) Serilog.Enrichers.CalllerInfo (콜 함수 이름, 라인 번호 남기기)
  • (선택) Serilog.Enrichers.Thread (쓰레드 정보 남기기)

Visual Studio 패키지 관리자 콘솔에서:

1
2
3
4
5
6
7
Install-Package Serilog
Install-Package Serilog.Sinks.File
Install-Package Serilog.Sinks.Console
Install-Package Serilog.Sinks.Async
Install-Package Serilog.Exceptions
Install-Package Serilog.Enrichers.CalllerInfo
Install-Package Serilog.Enrichers.Thread

3. 가장 단순한 콘솔 에제

Serilog 기본 감 잡기용으로, 콘솔 앱 에제를 먼저 볼게요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using Serilog;
using Serilog.Events;
using Serilog.Exceptions;

class Program
{
    static void Main(string[] args)
    {
        // 1) 전역 로거 구성
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Debug() // 최소 로그 레벨
            .Enrich.FromLogContext()
            .Enrich.WithExceptionDetails() // 예외 정보 자세히
            // 콘솔 출력
            .WriteTo.Console()
            // 파일 출력 (하루에 한 파일씩)
            .WriteTo.Async(a => a.File(
                path: "logs/app-.log",
                rollingInterval: RollingInterval.Day,
                retainedFileCountLimit: 14
            ))
            .CreateLogger();

        try
        {
            Log.Information("프로그램 시작. args={@Args}", args);

            // 예제 코드
            int a = 10;
            int b = 0;
            Log.Debug("나누기 실행 전. a={A}, b={B}", a, b);

            var result = a / b; // 예외 발생

            Log.Information("결과: {Result}", result);
        }
        catch (Exception ex)
        {
            Log.Error(ex, "예외 발생! 뭔가 잘못됐습니다.");
        }
        finally
        {
            Log.CloseAndFlush();
        }
    }
}

포인트 정리

  • MinimunLevel.Debug()
  • Debug 이상 (Debug, Information, Warning, Error, Fatal) 로그를 남김
  • WriteTo.Async(...)
  • 파일 기록을 비동기로 처리해서, 성능과 UI 끊김을 줄임
  • Log.Information("...{값}", value)
  • {} 안에 들어가는 건 “필드 이름”이 되고, Serilog 내부에서는 구조화된 데이터로 저장됨
  • "args={@Args}" 처럼 앞에 @를 쓰면 객체를 구조적으로 기록

4.로그 레벨 간단 정리

Serilog (그리고 대부분 로깅 라이브러리)는 로그 레벨을 아래처럼 씁니다.

  • Verbose: 디버깅용 아주 상세한 로그
  • Debug: 개발 중 디버깅용
  • Information: 정상적인 흐름(사용자 행동, 상태 변화)
  • Warning: 이상 징후, 하지만 아직 동작은 됨
  • Error: 기능이 실패, 예외 발생 등
  • Fatal: 시스템 전체에 치명적인 오류 (앱 다운 수준)

실무에서 많이 쓰는 패턴:

  • 로컬 개발: `MinimumLevel.Debug()
  • 운영 서버: MinimumLevel.Information() 또는 Warning()
    이렇게 두고, appsettings.json 이나 환경변수로 바꿔주기도 합니다.

5. 파일 로그, 롤링 설정 예시

조금 더 실무스럽게 파일 설정을 보자면:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .Enrich.FromLogContext()
    .Enrich.WithExceptionDetails()
    .WriteTo.Async(a => a.File(
        path: "logs/app/app-.log",
        rollingInterval: RollingInterval.Day,     // 매일 새로운 파일
        retainedFileCountLimit: 30,              // 30일치 보관
        fileSizeLimitBytes: 50 * 1024 * 1024,    // 파일 50MB 넘으면 분할
        rollOnFileSizeLimit: true,               // 사이즈 초과 시 app-20251113_001.log 이런 식으로
        shared: true,
        encoding: Encoding.UTF8
    ))
    .CreateLogger();

6. WPF에서 Serilog 적용하기

WPF에서는 앱이 시작될 때 한번만 로거를 구성하고,
각 ViewModel이나 서비스에서 Log.Information(...) 을 사용하는 방식이 깔끔합니다.

6-1. App.xaml.cs 에서 전역 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
using Serilog;
using Serilog.Exceptions;
using Serilog.Events;
using System;
using System.Windows;
using System.Text;

public partial class App : Application
{
    public App()
    {
        ConfigureLogging();
        RegisterGlobalExceptionHandlers();
    }

    private void ConfigureLogging()
    {
        Log.Logger = new LoggerConfiguration()
            // Microsoft, System 로그는 Warning 이상만
            .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
            .MinimumLevel.Debug()
            .Enrich.FromLogContext()
            .Enrich.WithExceptionDetails()
            .WriteTo.Async(a => a.File(
                path: "logs/app/app-.log",
                rollingInterval: RollingInterval.Day,
                retainedFileCountLimit: 14,
                encoding: Encoding.UTF8
            ))
            .CreateLogger();

        Log.Information("===== WPF App 시작 =====");
    }

    private void RegisterGlobalExceptionHandlers()
    {
        // WPF UI 스레드 예외
        this.DispatcherUnhandledException += (s, e) =>
        {
            Log.Fatal(e.Exception, "DispatcherUnhandledException");
            e.Handled = true; // 앱을 계속 살려둘지 여부는 상황에 따라
        };

        // Task에서 처리 안 된 예외
        AppDomain.CurrentDomain.UnhandledException += (s, e) =>
        {
            if (e.ExceptionObject is Exception ex)
            {
                Log.Fatal(ex, "UnhandledException");
            }
        };
    }

    protected override void OnExit(ExitEventArgs e)
    {
        Log.Information("===== WPF App 종료 =====");
        Log.CloseAndFlush();
        base.OnExit(e);
    }
}

6-2. ViewModel / 서비스에서 사용

간단하게 그냥 static Log를 직접 사용해도 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using Serilog;

public class MainWindowViewModel
{
    public MainWindowViewModel()
    {
        Log.Information("MainWindowViewModel 생성됨");
    }

    public void OnClickStart()
    {
        Log.Information("Start 버튼 클릭됨");
        try
        {
            // 작업...
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Start 작업 중 예외 발생");
        }
    }
}

나중에 규모가 커지면 ILogger 를 DI로 주입해서 쓰는 방식(HostBuilder + Microsoft.Extensions.Logging 연동)으로 확장하면 됩니다.

7. 구조화 로그 (파라미터 활용하기)

Serilog의 핵심 장점이 바로 Structured Logging입니다.

1
Log.Information("사용자 로그인. UserId={UserId}, Ip={Ip}", userId, ip);

위 코드는 내부적으로는:

  • 메시지 텍스트: "사용자 로그인. UserId=123, Ip=1.2.3.4"
  • 구조화 필드:
  • UserId: 123
  • Ip: "1.2.3.4"
    두 가지를 모두 기록해줍니다.

나중에 Seq나 ElasticSearch 같은 데 연동하면
“UserId=123인 로그만 보기” “Ip=1.2.3.4인 에러만 보기” 같은 필터링이 엄청 쉬워집니다.

복잡한 객체는 @를 붙여서:

1
Log.Information("주문 생성. {@Order}", orderDto);

이렇게 남겨두면, orderDto 안의 필드들이 전체 다 구조화되어 로그에 찍힙니다.

8. 자주 쓰는 패턴/팁 정리

8-1. 로그 폴더를 역할별로 나누기

예: UI 로그와 통신 로그를 분리하고 싶을 때

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static class LogService
{
    public enum LogType { App, Comm }
    private const string LogTypeProperty = "LogType";

    public static readonly ILogger App;
    public static readonly ILogger Comm;

    static LogService()
    {
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Debug()
            .Enrich.FromLogContext()
            .WriteTo.Logger(lc => lc
                .Filter.ByIncludingOnly(e =>
                    e.Properties.TryGetValue(LogTypeProperty, out var v) &&
                    v.ToString() == "\"App\"")
                .WriteTo.File("logs/app/app-.log",
                    rollingInterval: RollingInterval.Day))
            .WriteTo.Logger(lc => lc
                .Filter.ByIncludingOnly(e =>
                    e.Properties.TryGetValue(LogTypeProperty, out var v) &&
                    v.ToString() == "\"Comm\"")
                .WriteTo.File("logs/comm/comm-.log",
                    rollingInterval: RollingInterval.Day))
            .CreateLogger();

        App  = Log.ForContext(LogTypeProperty, LogType.App);
        Comm = Log.ForContext(LogTypeProperty, LogType.Comm);
    }
}

사용은:

1
2
LogService.App.Information("버튼 클릭");
LogService.Comm.Information("송신 JSON: {Json}", json);

이렇게 하면 logs/app 에는 UI/일반 로그, logs/comm/ 에는 통신 로그만 쌓이게 할 수 있습니다.


9. 마무리

요약하면:

  1. Serilog 설치(Serilog + File/Console/Async/Exceiptions)
  2. Log.Logger = new LoggerConfiguration()... 로 전역 로거 구성
  3. 콘솔/파일 Sink 추가
  4. WPF 라면 App.xaml.cs에서 한 번만 설정 + 전역 예외 핸들링
  5. 실체 코드에서는 Log.Information,Log.Error(ex,"...") 사용
  6. {Property} 형식으로 구조화 로그를 적극 활용
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.