클래스 기반 프로그램 구조에서는 로깅(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의 도움을 받아 작성되었습니다.
'Language > C#' 카테고리의 다른 글
C# DI 컨테이너 services와 provider 완전 정리 (0) | 2025.04.28 |
---|---|
C# DI(Dependency Injection)와 DI 컨테이너 정리 (0) | 2025.04.27 |