본문 바로가기

로깅이 아니라 트레이싱 분산 환경에서 언어별로 로그를 구조화하는 고급 디버깅 스킬

storybust 님의 블로그 2025. 11. 23.

분산 시스템을 운영하다 보면 “로그는 있는데, 원인을 못 찾겠다”는 상황을 자주 겪습니다. 저도 마이크로서비스 환경에서 API 호출이 꼬였을 때, 수천 줄의 로그를 뒤져도 흐름이 보이지 않아 답답했던 경험이 있습니다. 그때 깨달았죠. 로깅(logging)은 기록이고, 트레이싱(tracing)은 맥락이다. 단순히 로그를 남기는 것만으로는 복잡한 시스템의 흐름을 파악할 수 없습니다. 서비스 간 호출 관계, 요청의 전파, 지연 구간을 한눈에 볼 수 있어야 진짜 디버깅이 가능하죠. 이번 글에서는 Python, Java, Go, Node.js 등 언어별로 로그를 구조화하고 트레이싱으로 확장하는 실전 스킬을 공유합니다.

 

 

로그는 텍스트가 아니라 데이터다

많은 개발자가 아직도 로그를 문자열로만 남깁니다. 하지만 분산 환경에서는 로그를 데이터로 구조화해야 합니다. 예를 들어 JSON 포맷으로 로그를 남기면, 필드 단위로 검색과 필터링이 가능해집니다. 저는 Python에서 structlog를 사용해 로그를 JSON 형태로 남기고, Kibana에서 trace_id로 필터링하니 문제 구간을 10배 빠르게 찾을 수 있었습니다. Java에서는 logback의 JSONLayout, Go에서는 zap이나 zerolog, Node.js에서는 pino 같은 라이브러리를 활용하면 됩니다. 중요한 건 로그를 사람이 읽기 좋게 쓰는 게 아니라, 시스템이 분석하기 좋게 남기는 것입니다.

 

 

트레이싱은 로그의 시간축을 연결한다

트레이싱은 단순히 로그를 모으는 게 아니라, 요청의 흐름을 시간 순서로 시각화하는 기술입니다. 예를 들어 A 서비스가 B를 호출하고, B가 C를 호출하는 구조라면, 각 요청에 동일한 trace_id를 부여해 전체 호출 체인을 추적할 수 있습니다. 저는 OpenTelemetry를 도입하면서 이 개념을 완전히 이해했습니다. Python에서는 opentelemetry-instrumentation-flask, Java에서는 Micrometer Tracing, Go에서는 otelhttp, Node.js에서는 @opentelemetry/api를 사용하면 자동으로 트레이스 컨텍스트를 전파할 수 있습니다. 트레이싱을 적용하면 단순한 로그 집합이 아니라, 시스템의 흐름을 한눈에 볼 수 있는 지도가 만들어집니다.

 

 

언어별 트레이싱 설계 포인트

  • Python: structlog와 OpenTelemetry를 함께 사용하면, 로그와 트레이스가 자연스럽게 연결됩니다. 특히 비동기 코드에서는 contextvars를 활용해 trace_id를 전파하세요.
  • Java: Slf4j MDC를 이용해 trace_id를 스레드 로컬에 저장하면, 로그마다 자동으로 컨텍스트가 붙습니다. Spring Boot 3 이상에서는 Micrometer Tracing이 기본 통합되어 있습니다.
  • Go: 고루틴 간 컨텍스트 전파가 핵심입니다. context.Context에 trace 정보를 담고, 모든 함수 호출에 전달해야 합니다. otelhttp 미들웨어를 사용하면 자동화가 가능합니다.
  • Node.js: 비동기 호출이 많기 때문에 AsyncLocalStorage를 이용해 trace_id를 유지하세요. pino와 OpenTelemetry를 함께 쓰면 로그와 트레이스가 자연스럽게 연결됩니다.

 

로그의 수준보다 중요한 건 ‘맥락’이다

많은 팀이 로그 레벨(INFO, WARN, ERROR)에만 집중하지만, 실제로 중요한 건 맥락(Context)입니다. 예를 들어 “DB 연결 실패”라는 로그보다 “어떤 요청이, 어떤 사용자 세션에서, 어떤 서비스 경로를 거쳐 실패했는가”가 훨씬 유의미하죠. 저는 로그에 항상 trace_id, span_id, user_id, service_name을 포함시키는 습관을 들였습니다. 이렇게 하면 단일 로그가 아니라 하나의 사건 흐름으로 문제를 추적할 수 있습니다.

 

 

트레이싱 도입 시 피해야 할 함정

  • 1. 로그와 트레이스를 분리하지 말라: 트레이싱을 도입해도 로그가 따로 놀면 의미가 없습니다. 동일한 trace_id를 공유해야 합니다.
  • 2. 모든 요청을 추적하려 하지 말라: 트레이싱은 비용이 큽니다. 핵심 경로나 장애 가능성이 높은 구간부터 점진적으로 적용하세요.
  • 3. 시각화 도구에 의존하지 말라: Jaeger나 Zipkin은 훌륭하지만, 트레이스 데이터의 품질이 나쁘면 아무 소용이 없습니다. 데이터 구조가 먼저입니다.
  • 4. 로그를 ‘기록’이 아닌 ‘관찰’로 생각하라: 로그는 사후 분석이 아니라, 실시간 관찰의 도구가 되어야 합니다.

 

트레이싱은 시스템의 ‘감각’을 만드는 기술이다

결국 로깅과 트레이싱의 차이는 기록과 감각의 차이입니다. 로깅은 과거를 남기지만, 트레이싱은 현재를 보여줍니다. 분산 환경에서는 단일 로그보다 흐름의 연속성이 훨씬 중요합니다. 저는 이제 로그를 남길 때마다 “이 로그가 다른 서비스와 연결될 수 있을까?”를 먼저 생각합니다. 그 질문 하나가 시스템을 더 투명하게 만들고, 장애 대응 속도를 획기적으로 높여줍니다. 트레이싱은 단순한 기술이 아니라, 시스템을 이해하는 새로운 시각입니다.

댓글