본문 바로가기
Language/C#

C#에서 로깅 서비스를 모듈화하는 방법

by Wikinist 2025. 4. 27.

 

클래스 기반 프로그램 구조에서는 로깅(logging) 기능을 별도의 서비스로 분리하는 것이
유지보수, 테스트, 확장성 모두에 큰 장점을 가져옵니다.

이 글에서는 C#에서 로깅을 모듈화하는 방법을 순서대로 소개합니다.


1. ILogger 인터페이스 정의

가장 먼저, 로깅 기능의 일관성을 위해 인터페이스를 정의합니다.

public interface ILogger
{
    void Log(string message);
}

ILogger는 "로그를 남긴다"는 하나의 역할만 담당합니다.
이 인터페이스를 기반으로 다양한 로거 클래스를 만들어 사용할 수 있습니다.


2. 로거 구현체 만들기

인터페이스를 구현한 실제 클래스를 만들어야 합니다.

2.1 UI에 출력하는 로거

Windows Forms의 ListBox에 로그를 출력하는 예시입니다.
멀티 스레드 환경에서도 안전하게 동작하도록 Invoke를 사용합니다.

public class UiLogger : ILogger
{
    private readonly ListBox _listBox;
    public UiLogger(ListBox listBox)
    {
        _listBox = listBox;
    }

    public void Log(string message)
    {
        string timestamped = DateTime.Now.ToString("[HH:mm:ss] ") + message;

        void Add()
        {
            _listBox.Items.Add(timestamped);
            _listBox.HorizontalScrollbar = true;
            _listBox.TopIndex = _listBox.Items.Count - 1;
        }

        if (_listBox.InvokeRequired)
            _listBox.Invoke((Action) Add);
        else
            Add();
    }
}

2.2 파일에 기록하는 로거

날짜별로 파일을 생성해서 로그를 저장하는 로거입니다.

public class FileLogger : ILogger
{
    private readonly string _logDirectory;
    public FileLogger(string logDirectory)
    {
        _logDirectory = logDirectory;
        Directory.CreateDirectory(_logDirectory);
    }

    public void Log(string message)
    {
        string timestamped = DateTime.Now.ToString("[HH:mm:ss] ") + message;
        string file = Path.Combine(_logDirectory, DateTime.Now.ToString("yyyy_MM_dd") + ".log");
        File.AppendAllText(file, timestamped + Environment.NewLine);
    }
}

2.3 여러 로거를 동시에 사용하는 CompositeLogger

UI파일 양쪽 모두에 동시에 로그를 남기고 싶을 때 사용할 수 있습니다.

public class CompositeLogger : ILogger
{
    private readonly IEnumerable<ILogger> _loggers;
    public CompositeLogger(params ILogger[] loggers)
    {
        _loggers = loggers;
    }

    public void Log(string message)
    {
        foreach (var lg in _loggers)
            lg.Log(message);
    }
}

3. 로깅 서비스 적용 방법

프로그램 시작 시 다음처럼 로거들을 초기화하고 묶어 사용합니다.

// 프로그램 초기화 시
var uiLogger   = new UiLogger(lb_log);
var fileLogger = new FileLogger(Path.Combine(Application.StartupPath, "log"));
ILogger logger = new CompositeLogger(uiLogger, fileLogger);

// 이후 TcpServer, ClientHandler, MessageProcessor 등에 주입
var programService = new ProgramService(appSettings.TargetProgramPaths, logger);
var messageProcessor = new MessageProcessor(programService, logger);
var clientHandler = new ClientHandler(messageProcessor, clientList, logger);
var tcpServer = new TcpServer(serverIP, port, clientHandler, logger);

각 클래스 내부에서는 단순히

logger.Log("로그 메시지");

로 사용하면 됩니다.
AppendLog() 같은 직접 호출 방식 대신, ILogger 인터페이스만 의존하면 됩니다.


4. 로깅 모듈화의 장점

항목 설명
의존성 명시 생성자 주입(Dependency Injection) 친화적
테스트 용이 Mock ILogger로 검증 가능
단일 책임 원칙(SRP) 준수 클래스가 본연의 역할에 집중 가능
확장성 확보 콘솔, 파일, DB, 클라우드 등으로 쉽게 확장 가능

5. 대표적인 프레임워크 사례

5.1 .NET Core / ASP.NET Core

public class MyService
{
    private readonly ILogger<MyService> _logger;

    public MyService(ILogger<MyService> logger)
    {
        _logger = logger;
    }

    public void DoSomething()
    {
        _logger.LogInformation("Doing something...");
    }
}

ILogger<T> 형태로 주입받고, 로그 레벨(Information, Error, Warning 등)을 구분하여 사용합니다.

5.2 Unity / Autofac / SimpleInjector

DI 컨테이너를 사용하는 경우에도 로거는 생성자 주입으로 자동 등록되며,
로그 레벨이나 필터링은 설정 파일로 일괄 관리합니다.


6. 언제 이 방식을 안 쓰는가?

  • 정말 단순한 유틸성 클래스일 때
  • 로깅이 필요 없는 경우
  • static 전역 로거가 이미 설정된 경우 (LogManager.GetLogger(...) 등)

하지만 대부분의 경우에는 생성자 주입 방식이
테스트성, 확장성, 유지보수성 면에서 훨씬 유리합니다.


해당 게시글은 ChatGPT의 도움을 받아 작성되었습니다.