1. 소개
프로그래밍에서 로깅은 단순히 "콘솔에 뭐라도 찍는 것"이 아니야. 로깅은 블랙박스이자 GPS 트래커이고 계기판이기도 해. 로그가 없으면 큰 프로그램은 미스테리한 블랙박스가 돼: "서비스가 왜 멈췄지?", "사용자가 메일을 못 받았는데 왜?", "이 모듈을 지난 3개월 동안 누가 실행이라도 했나?" — 이런 질문들에 답하려면 로그가 저장되고 접근 가능해야 해.
현실에서 로깅이 왜 필요할까?
버그 탐지와 진단
프로그램이 죽었다면 — 로그가 어디서, 왜 그런지 알려줘. 죽지는 않았지만 이상하게 동작한다면 — 로그가 그 상황에 이르게 한 단계들을 보여줄 거야.
상태 모니터링
로그로 현재 시스템이 동작 중인지, 서버에 요청이 많은지, 오류가 발생하는지, 누가 무엇을 하고 있는지 알 수 있어.
보안
권한 없는 접근 시도, 의심스러운 활동, 인증 오류 등을 로깅해서 추적할 수 있어.
감사(audit)
누가, 무엇을, 언제 했는지. 악성 관리자(예: 해고된)가 데이터를 지웠다면 로그로 복구하거나 최소한 어떤 행동을 했는지 파악할 수 있어.
개발 및 운영 지원
로그는 개발자뿐만 아니라 테스터, 운영자, 시스템 관리자에게도 유용해. 반년 뒤에 너 자신이 이렇게 말할거야: '로그 추가하길 잘했어!'
Console.WriteLine부터 프로덕션 로깅까지
초보자의 첫 반응: "그냥 Console.WriteLine이면 안 돼?" 작은 학습용 프로그램에선 그걸로 충분할 수 있어. 하지만 앱이 서버에서 돌아가고, 동시에 여러 쓰레드/요청을 처리하거나 수십, 수백 명이 쓰면 콘솔만으론 부족해. 필요한 것들:
- 중요한 정보와 디버깅 정보를 분리
- 로그를 콘솔뿐 아니라 파일, DB, 중앙 집약형 시스템으로 보낼 수 있어야 함
- 로그 상세도(level)를 조절(최소 — 오류만, 최대 — 전부)
- 자동으로 날짜/시간, 부가 정보 추가
- 유연한 설정
- 무엇보다도 — 로그 전송 대상을 바꾸려 코드에서 매번 수정하지 않기
바로 여기서 '진짜' 로거들의 시대가 시작돼.
2. .NET에서의 현대 로깅 기초
.NET 생태계에는 강력하고 유연한 표준 로깅 프레임워크가 있어 — Microsoft.Extensions.Logging. ASP.NET Core와 함께 등장한 "뉴 웨이브" .NET 라이브러리의 일부지만, 이제는 서버, 데스크탑, 모바일 어디서나 쓰여.
이 프레임워크의 장점은?
- 추상화 — 특정 구현에 묶이지 않아(로거는 파일, 콘솔, 클라우드에 쓰거나 동시에 여러 곳에 보낼 수 있음).
- 로그 레벨 지원(Trace, Debug, Information, Warning, Error, Critical).
- DI 컨테이너에 통합되어 모든 최신 .NET 앱에서 사용하기 쉬움.
- 구조화 로깅, 고급 포매터, 다양한 통합을 지원하는 풍부한 확장 생태계.
주요 개념과 클래스
이제 Microsoft.Extensions.Logging로 작업할 때 필요한 핵심 객체들을 살펴보자:
| 클래스/인터페이스 | 용도 |
|---|---|
|
클래스 타입으로 지정된 로깅 인터페이스 |
|
제네릭이 아닌 일반 로거 인터페이스 |
|
로거 인스턴스를 생성하는 팩토리 |
|
애플리케이션에서 로깅을 설정할 때 사용 |
|
로그 레벨 열거형 (Trace, Debug, Information, ...) |
로그 레벨
메시지를 중요도별로 나누면 "노이즈"를 필터링하고 필요한 걸 찾기 쉬워져:
| 레벨 (LogLevel) | 어떤 용도로? |
|---|---|
|
최대한 자세한 디버그 정보, 잡음, 타이밍 데이터 |
|
주요 디버깅 정보 |
|
정상 동작을 위한 주요 이벤트 메시지 |
|
잠재적 문제에 대한 경고, 시스템은 계속 동작 |
|
주의가 필요한 오류, 하지만 애플리케이션은 살아있음 |
|
시스템 전체에 위협이 되는 치명적 실패 |
3. 실습: 애플리케이션에 로깅 추가하기
추상적인 코드만 쓰지 말고, 데모 앱을 계속 키워보자. 이미 간단한 Calculator 클래스가 있고 강의를 따라가면서 이걸 확장한다고 하자.
예시: 기본 Calculator
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
// 나머지 메서드...
}
이제 여기에 로깅을 넣어보자. 외부(예: Dependency Injection, DI)에서 주입받을 ILogger<Calculator> 인터페이스가 필요해.
using Microsoft.Extensions.Logging;
public class Calculator
{
private readonly ILogger<Calculator> _logger;
public Calculator(ILogger<Calculator> logger)
{
_logger = logger;
}
public int Add(int a, int b)
{
int result = a + b;
_logger.LogInformation("덧셈 수행: {A} + {B} = {Result}", a, b, result);
return result;
}
}
흥미로운 사실:
문자열을 이어 붙이는 대신 로거는 템플릿과 명명된 파라미터({A}, {B}, {Result})를 지원해. 덕분에 로그를 구조화해서 자동 처리나 검색에 유리하게 만들 수 있어.
4. 콘솔 애플리케이션에서 로거 생성 및 설정하기
1. NuGet 패키지 추가
프로젝트에는 다음이 필요해:
- Microsoft.Extensions.Logging
- Microsoft.Extensions.Logging.Console (콘솔에 쓰려면)
- (선택) 필요한 경우 다른 provider들
2. 로거 설정
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
// DI 컨테이너 생성
var serviceProvider = new ServiceCollection()
.AddLogging(builder => {
builder.AddConsole(); // 콘솔 출력
builder.SetMinimumLevel(LogLevel.Debug); // 최소 로그 레벨
})
.BuildServiceProvider();
// 필요한 타입의 로거 인스턴스 획득
var logger = serviceProvider.GetRequiredService<ILogger<Calculator>>();
var calculator = new Calculator(logger);
calculator.Add(5, 3); // 로그에 메시지가 찍힐 거야
초보자들이 흔히 하는 실수
"왜 콘솔에 로그가 안 보일까?" — 필요한 provider(.AddConsole())를 추가했는지, 최소 로그 레벨(SetMinimumLevel)이 올바른지 확인해. 레벨이 더 높게 설정되면 메시지가 필터링될 수 있어!
5. 유용한 팁
오류와 비정상 상황 로깅하기
예를 들어 0으로 나누는 상황을 로깅하고 싶다면, 다음처럼 메서드를 추가해보자:
public int Divide(int a, int b)
{
if (b == 0)
{
_logger.LogError("0으로 나누기 시도! a={A}", a);
throw new DivideByZeroException();
}
int result = a / b;
_logger.LogInformation("나눗셈 수행: {A} / {B} = {Result}", a, b, result);
return result;
}
왜 이게 필요할까?
실제로 뭔가 잘못됐을 때 Error 레벨 로그는 보통 관리자의 주목을 끌어: 알림/경고로 자동 전송되거나 모니터링에서 강조되어 문제 대응에 사용돼.
카테고리와 메타데이터 사용하기 (scopes)
.NET 로거는 scopes라는 추가 메타데이터를 지원해 — 특정 코드 블록 동안 모든 로그에 자동으로 붙는 정보야. 예를 들어 웹 요청이나 사용자 세션을 처리할 때 식별자를 scope에 넣을 수 있어.
using (_logger.BeginScope("UserId: {UserId}", 42))
{
_logger.LogInformation("사용자 데이터 처리 시작");
// ...
}
블록 안의 모든 메시지에 UserId: 42라는 태그가 추가돼서 나중에 사용자별로 로그를 찾기 편해.
예시: 로그 레벨 동작
_logger.LogTrace("이건 Trace — 거의 안 보일 거야");
_logger.LogDebug("이건 Debug — 개발자용");
_logger.LogInformation("이건 Information — 정상 동작 이벤트");
_logger.LogWarning("이건 Warning — 잠재적 문제");
_logger.LogError("이건 Error — 주의가 필요한 에러");
_logger.LogCritical("이건 Critical — 시스템이 불타고 있어, 소방수 필요!");
만약 SetMinimumLevel(LogLevel.Information)로 설정했다면, Information, Warning, Error, Critical 레벨만 보일 거야.
팁:
개발 단계에서는 Trace와 Debug를 켜서 자세히 진단하고, 운영 환경에서는 보통 Information 이상만 켜서 로그 용량이 불필요하게 커지는 걸 막아.
비주얼 다이어그램: 현대 로깅 아키텍처
graph TD
A[애플리케이션 코드] --ILogger<YourClass>--> B[Microsoft.Extensions.Logging]
B --> C1[Console Provider]
B --> C2[File Provider]
B --> C3[Cloud/Database Provider]
C1 -.-> D1[콘솔 로그]
C2 -.-> D2[파일 로그]
C3 -.-> D3[모니터링 시스템에 로그]
subgraph Providers
C1
C2
C3
end
6. 추가 기능과 확장
구조화 로깅:
파라미터 값들을 문자열로만 저장하지 않고 키-값 형태로 유지하면(예: Seq, ELK/ElasticSearch, Application Insights) 해당 파라미터로 검색/집계가 가능해.
로깅 제공자들:
파일, Windows EventLog, Azure 등 수십 가지 provider를 추가할 수 있어.
appsettings.json을 통한 로깅 설정:
ASP.NET Core에서는 설정 파일로 로깅을 유연하게 구성할 수 있어서 앱 재컴파일 없이 레벨을 바꿀 수 있어.
예: 설정 파일에서 최소 로그 레벨 설정하기 (appsettings.json):
{
"Logging": {
"LogLevel": {
"Default": "Information",
"MyApp.Calculator": "Debug",
"Microsoft": "Warning"
}
}
}
7. Microsoft.Extensions.Logging 사용 시 흔한 실수
실수 #1: 잘못된 로그 레벨 선택.
LogInformation을 오류 상황에 쓰고 LogError나 LogCritical를 안 쓰면 프로덕션에서 문제 찾기 힘들어.
실수 #2: 구조화 로깅 무시.
문자열 연결으로 로그를 찍으면 ({Parameter} 같은 플레이스홀더 대신) 파라미터 기반 검색 등의 이점을 잃어.
실수 #3: 로그 레벨 설정을 잘못함.
최소 레벨이 너무 높게 설정되면(예: Warning로) 필요한 디버그 메시지가 필터링돼.
실수 #4: 로그가 너무 많거나 너무 적음.
운영 환경에서 Trace를 켜 두면 노이즈가 심해지고, 로그가 전혀 없으면 문제 진단이 불가능해.
GO TO FULL VERSION