Logging
로깅
애플리케이션 이벤트 기록. 디버깅, 모니터링에 필수. ELK 스택.
로깅
애플리케이션 이벤트 기록. 디버깅, 모니터링에 필수. ELK 스택.
Logging(로깅)은 애플리케이션이 실행되는 동안 발생하는 이벤트, 상태, 오류를 시간순으로 기록하는 것입니다. 로그는 디버깅, 모니터링, 보안 감사, 성능 분석에 필수적인 데이터 소스입니다. 잘 설계된 로깅 시스템은 장애 발생 시 원인을 빠르게 파악하게 해줍니다.
로그 레벨은 일반적으로 DEBUG, INFO, WARN, ERROR, FATAL로 구분합니다. 개발 환경에서는 DEBUG 레벨까지 상세히 기록하고, 프로덕션에서는 INFO 이상만 기록하여 로그 볼륨을 관리합니다. 로그 레벨은 환경 변수로 런타임에 변경할 수 있어야 장애 시 임시로 DEBUG 로그를 활성화할 수 있습니다.
구조화된 로그(Structured Logging)는 JSON 같은 형식으로 로그를 남겨 검색과 분석을 용이하게 합니다. 단순 텍스트 로그("User 123 logged in")보다 JSON 로그({"event": "login", "userId": 123, "ip": "1.2.3.4"})가 로그 분석 도구에서 필터링과 집계가 쉽습니다.
중앙 집중식 로그 관리는 현대 마이크로서비스에서 필수입니다. 각 서비스의 로그를 ELK 스택(Elasticsearch, Logstash, Kibana), Loki+Grafana, Datadog, Splunk 같은 시스템으로 수집하여 한곳에서 검색하고 대시보드로 시각화합니다. 분산 시스템에서는 trace ID를 로그에 포함하여 요청 흐름을 추적합니다.
# Python 구조화된 로깅 설정 (structlog)
import structlog
import logging
# 구조화된 로거 설정
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.UnicodeDecoder(),
# JSON 출력 (프로덕션)
structlog.processors.JSONRenderer()
],
wrapper_class=structlog.stdlib.BoundLogger,
context_class=dict,
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)
log = structlog.get_logger()
# 컨텍스트와 함께 로깅
def process_order(order_id: str, user_id: str):
# 요청별 컨텍스트 바인딩
log_ctx = log.bind(
order_id=order_id,
user_id=user_id,
trace_id=get_trace_id() # 분산 추적 ID
)
log_ctx.info("order_processing_started")
try:
result = payment_service.charge(order_id)
log_ctx.info(
"payment_completed",
amount=result.amount,
currency=result.currency
)
except PaymentError as e:
log_ctx.error(
"payment_failed",
error_code=e.code,
error_message=str(e)
)
raise
// 구조화된 로그 출력 예시 (JSON Lines)
{"timestamp": "2024-01-15T09:30:00.123Z", "level": "info", "event": "order_processing_started", "order_id": "ORD-12345", "user_id": "USR-789", "trace_id": "abc123", "service": "order-api"}
{"timestamp": "2024-01-15T09:30:00.456Z", "level": "info", "event": "payment_completed", "order_id": "ORD-12345", "user_id": "USR-789", "trace_id": "abc123", "amount": 59900, "currency": "KRW"}
{"timestamp": "2024-01-15T09:30:01.789Z", "level": "error", "event": "payment_failed", "order_id": "ORD-12346", "user_id": "USR-790", "trace_id": "def456", "error_code": "INSUFFICIENT_FUNDS", "error_message": "Card declined"}
// Node.js 구조화된 로깅 (Pino)
const pino = require('pino');
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
formatters: {
level: (label) => ({ level: label })
},
timestamp: pino.stdTimeFunctions.isoTime
});
// 자식 로거로 컨텍스트 추가
const reqLogger = logger.child({
requestId: req.id,
userId: req.user?.id
});
reqLogger.info({ path: req.path }, 'request received');
DevOps: "로그 스토리지 비용이 너무 많이 나와요. DEBUG 레벨 로그가 프로덕션에서 남고 있는 서비스가 있는 것 같은데..."
개발자: "결제 서비스에서 DEBUG가 켜져 있었네요. INFO로 바꾸면 로그 볼륨이 80% 줄 거예요."
시니어: "그리고 로그에 PII(개인정보) 안 남도록 마스킹 처리 확인해주세요. 이메일, 전화번호 같은 거요."
면접관: "분산 시스템에서 장애 추적은 어떻게 하셨나요?"
지원자: "모든 요청에 trace ID를 생성해서 로그에 포함시켰습니다. Kibana에서 trace ID로 검색하면 해당 요청이 거쳐간 모든 서비스의 로그를 시간순으로 볼 수 있어요. 나중에는 OpenTelemetry로 전환해서 트레이싱과 로깅을 통합했습니다."
리뷰어: "catch 블록에서 `console.log(error)` 대신 로거 사용해주세요. 그리고 에러 객체 전체를 남기면 스택트레이스도 검색 가능해져요."
작성자: "`logger.error({ err, orderId }, 'Failed to process order')` 이런 형태로 수정하겠습니다."