본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 5장: 로그 통합과 상관관계
2026년 2월 16일·인프라·

5장: 로그 통합과 상관관계

OpenTelemetry 로그 데이터 모델, 로그-트레이스 상관관계, 기존 로거 브릿지(Python logging, Go slog), 구조화 로그와 로그 레벨 전략을 학습합니다.

14분613자8개 섹션
monitoringobservability
공유
opentelemetry5 / 11
1234567891011
이전4장: 메트릭(Metrics) 수집과 분석다음6장: OTel SDK 계측 실전

학습 목표

  • OpenTelemetry 로그 데이터 모델의 구조를 이해합니다
  • 로그-트레이스 상관관계(Correlation)의 원리와 구현 방법을 학습합니다
  • 기존 로거(Python logging, Go slog)와의 브릿지 패턴을 파악합니다
  • 구조화 로그(Structured Logging)의 설계 원칙을 익힙니다
  • 환경별 로그 레벨 전략을 수립할 수 있습니다

OpenTelemetry의 로그 접근 방식

OpenTelemetry는 트레이스와 메트릭과 달리 로그에 대해 독자적인 로깅 프레임워크를 제공하지 않습니다. 대신 기존에 널리 사용되는 로깅 라이브러리(Python logging, Go slog, Java Log4j 등)와 통합하는 브릿지(Bridge) 접근 방식을 채택했습니다.

이 설계 결정의 이유는 명확합니다. 이미 모든 언어와 프레임워크에는 성숙한 로깅 라이브러리가 존재하며, 개발자들은 이미 익숙한 로깅 패턴을 가지고 있습니다. OTel이 새로운 로깅 API를 강요하는 대신, 기존 로거에서 생성된 로그를 OTel 파이프라인으로 연결하는 것이 현실적입니다.


로그 데이터 모델

OpenTelemetry의 로그 레코드는 다음 필드로 구성됩니다.

필드타입설명
timestamp나노초로그 발생 시각
observed_timestamp나노초로그 수집 시각
severity_number정수심각도 수치 (1-24)
severity_text문자열심각도 텍스트 (DEBUG, INFO, WARN, ERROR 등)
bodyAny로그 메시지 본문
attributesMap로그 속성 키-값 쌍
resourceResource로그를 생성한 리소스 정보
instrumentation_scopeScope계측 라이브러리 정보
trace_id바이트연관된 트레이스 ID
span_id바이트연관된 스팬 ID
trace_flags바이트트레이스 플래그 (샘플링 여부)

trace_id와 span_id 필드가 로그-트레이스 상관관계의 핵심입니다. 로그가 트레이스 컨텍스트 내에서 생성되면, 해당 트레이스와 스팬의 ID가 자동으로 주입됩니다.


로그-트레이스 상관관계

로그-트레이스 상관관계는 "이 에러 로그가 어떤 요청에서 발생했는가"를 즉시 파악할 수 있게 해주는 핵심 기능입니다.

상관관계의 워크플로우

  1. 에러 로그가 발생합니다
  2. 해당 로그에 trace_id가 자동 첨부되어 있습니다
  3. trace_id로 트레이스를 조회하여 전체 요청 경로를 파악합니다
  4. 실패한 스팬을 찾아 근본 원인을 분석합니다

Python logging 브릿지

Python의 표준 logging 모듈과 OTel을 연결하는 방법입니다.

python_log_bridge.py
python
import logging
from opentelemetry import trace
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
from opentelemetry.sdk.resources import Resource
 
# 리소스 정의
resource = Resource.create({
    "service.name": "user-service",
    "service.version": "1.2.0",
})
 
# LoggerProvider 초기화
logger_provider = LoggerProvider(resource=resource)
logger_provider.add_log_record_processor(
    BatchLogRecordProcessor(
        OTLPLogExporter(endpoint="http://collector:4317")
    )
)
 
# Python logging에 OTel 핸들러 추가
handler = LoggingHandler(
    level=logging.INFO,
    logger_provider=logger_provider,
)
 
# 기존 로거에 핸들러 연결
logger = logging.getLogger("user-service")
logger.addHandler(handler)
logger.setLevel(logging.INFO)

이제 기존 방식대로 로그를 작성하면 OTel 파이프라인을 통해 전송됩니다.

log_with_correlation.py
python
from opentelemetry import trace
 
tracer = trace.get_tracer("user-service")
logger = logging.getLogger("user-service")
 
def get_user(user_id):
    with tracer.start_as_current_span("get-user") as span:
        span.set_attribute("user.id", user_id)
        
        logger.info("사용자 조회 시작", extra={"user_id": user_id})
        
        try:
            user = db.find_user(user_id)
            logger.info("사용자 조회 성공", extra={"user_id": user_id})
            return user
        except UserNotFoundError:
            logger.warning("사용자를 찾을 수 없음", extra={"user_id": user_id})
            raise
        except Exception as e:
            logger.error("사용자 조회 실패", extra={"user_id": user_id, "error": str(e)})
            raise
Info

OTel Log Bridge가 활성화되면, 트레이스 컨텍스트 내에서 생성된 로그에 trace_id와 span_id가 자동으로 주입됩니다. 별도의 코드 변경 없이 기존 logging.info(), logging.error() 호출이 그대로 동작합니다.

Go slog 브릿지

Go 1.21부터 표준 라이브러리에 포함된 slog 패키지와의 통합입니다.

go_slog_bridge.go
go
package main
 
import (
    "context"
    "log/slog"
 
    "go.opentelemetry.io/contrib/bridges/otelslog"
    "go.opentelemetry.io/otel"
)
 
func main() {
    // OTel LoggerProvider 설정 (생략)
    
    // slog에 OTel 핸들러 연결
    handler := otelslog.NewHandler("my-service")
    logger := slog.New(handler)
    slog.SetDefault(logger)
    
    // 기존 방식으로 로그 작성 -- trace_id 자동 주입
    tracer := otel.Tracer("my-service")
    ctx, span := tracer.Start(context.Background(), "process-request")
    defer span.End()
    
    slog.InfoContext(ctx, "요청 처리 시작",
        slog.String("request.id", "req-001"),
        slog.Int("retry.count", 0),
    )
}

구조화 로그 설계

비구조화 vs 구조화 로그

비구조화 로그
text
2026-03-23 14:30:22 ERROR Failed to process order ORD-123 for user usr-456: insufficient funds
구조화 로그
json
{
  "timestamp": "2026-03-23T14:30:22.123Z",
  "severity": "ERROR",
  "body": "Failed to process order",
  "attributes": {
    "order.id": "ORD-123",
    "user.id": "usr-456",
    "error.type": "InsufficientFundsError",
    "payment.amount": 50000,
    "payment.balance": 30000
  },
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "00f067aa0ba902b7"
}

구조화 로그의 장점은 명확합니다.

  • 검색 가능 — order.id = "ORD-123"으로 정확한 필터링
  • 집계 가능 — error.type별 에러 횟수 통계
  • 상관 가능 — trace_id로 트레이스 연결
  • 파싱 불필요 — 정규식으로 텍스트를 파싱할 필요 없음

구조화 로그 작성 원칙

Tip

로그 메시지 본문(body)은 사람이 읽기 위한 고정 텍스트로 작성하고, 가변 데이터는 모두 속성(attributes)으로 분리하세요. 이렇게 하면 로그 메시지 패턴으로 그룹화가 가능하고, 속성으로 필터링과 집계가 가능합니다.

structured_logging_patterns.py
python
# 나쁜 예 -- 가변 데이터가 메시지에 포함
logger.error(f"주문 {order_id} 결제 실패: {error_message}, 금액: {amount}")
 
# 좋은 예 -- 고정 메시지 + 구조화 속성
logger.error(
    "주문 결제 실패",
    extra={
        "order.id": order_id,
        "error.message": error_message,
        "payment.amount": amount,
        "payment.currency": "KRW",
    }
)

로그 레벨 전략

로그 레벨 정의

OpenTelemetry는 24단계의 심각도 수치를 정의하지만, 실무에서는 다음 5단계를 주로 사용합니다.

레벨OTel 수치용도
DEBUG5개발 환경 디버깅 정보
INFO9정상 동작 확인 (요청 시작/완료, 상태 변경)
WARN13잠재적 문제 (재시도 발생, 임계값 근접)
ERROR17처리 실패 (요청 에러, 외부 서비스 장애)
FATAL21프로세스 종료가 필요한 심각한 오류

환경별 로그 레벨 전략

환경별 로그 레벨 설정
yaml
# 개발 환경 -- 상세 디버깅 가능
development:
  default_level: DEBUG
  noisy_libraries: INFO     # ORM, HTTP 클라이언트 등
 
# 스테이징 환경 -- 프로덕션과 유사하되 INFO 허용
staging:
  default_level: INFO
  noisy_libraries: WARN
 
# 프로덕션 환경 -- 비용 최적화
production:
  default_level: INFO
  noisy_libraries: ERROR
  high_traffic_paths: WARN   # 초당 수천 건 발생하는 경로

Collector에서 로그 레벨 필터링

프로덕션 환경에서 불필요한 로그를 Collector 수준에서 필터링할 수 있습니다.

collector-log-filter.yaml
yaml
processors:
  filter/logs:
    logs:
      log_record:
        # DEBUG 로그 제거
        - severity_number < 9
        
  attributes/logs:
    actions:
      # 민감 정보 마스킹
      - key: user.email
        action: hash
      - key: request.body
        action: delete
        
service:
  pipelines:
    logs:
      receivers: [otlp]
      processors: [filter/logs, attributes/logs, batch]
      exporters: [otlp/loki]

로그 비용 관리

로그는 3대 신호 중 저장 비용이 가장 높은 신호입니다. 효과적인 비용 관리 전략이 필수입니다.

로그 볼륨 줄이기

  1. 로그 레벨 조정 — 프로덕션에서 DEBUG 로그 비활성화
  2. 샘플링 — 정상 요청의 로그는 일부만 수집
  3. 필터링 — 헬스 체크, 정적 리소스 등 불필요한 로그 제거
  4. 속성 정리 — 불필요하게 큰 속성(request body, response body) 제거 또는 잘라내기
collector-log-cost.yaml
yaml
processors:
  filter/healthcheck:
    logs:
      log_record:
        # 헬스 체크 로그 제거
        - IsMatch(body, ".*healthcheck.*")
        - IsMatch(body, ".*readiness.*")
  
  transform/truncate:
    log_statements:
      - context: log
        statements:
          # 로그 본문이 4KB를 초과하면 잘라내기
          - truncate_all(attributes, 4096)

로그 티어링

모든 로그를 동일한 저장소에 보관할 필요는 없습니다.

티어저장소보존 기간대상
HotLoki (SSD)7일ERROR, WARN, 최근 로그
WarmLoki (HDD)30일INFO
ColdS3 / GCS1년감사 로그, 규정 준수
Warning

로그에 개인정보(PII)가 포함되지 않도록 주의하세요. 이메일, 전화번호, 주민번호 등은 로그에 기록하기 전에 마스킹하거나 해싱해야 합니다. Collector의 attributes 프로세서에서 hash 액션을 활용하면 중앙에서 일괄 처리할 수 있습니다.


정리

이번 장에서는 OpenTelemetry의 로그 통합 접근 방식을 학습했습니다. OTel은 독자적인 로깅 프레임워크 대신 기존 로거와의 브릿지를 제공하며, trace_id와 span_id를 자동 주입하여 로그-트레이스 상관관계를 구현합니다. 구조화 로그의 설계 원칙과 환경별 로그 레벨 전략, 비용 관리 방법도 살펴보았습니다.

다음 장에서는 OTel SDK를 활용한 실전 계측을 다룹니다. 자동 계측과 수동 계측의 차이, Python/Node.js/Go 각 언어별 SDK 활용법, 커스텀 스팬과 메트릭 생성 실습을 진행합니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#monitoring#observability

관련 글

인프라

6장: OTel SDK 계측 실전

자동 계측과 수동 계측의 차이를 이해하고, Python/Node.js/Go 각 언어별 SDK 활용법과 커스텀 스팬/메트릭 생성을 실습합니다.

2026년 2월 18일·11분
인프라

4장: 메트릭(Metrics) 수집과 분석

OpenTelemetry 메트릭의 종류(Counter, Gauge, Histogram), 카디널리티 관리, Exemplars를 통한 메트릭-트레이스 연결, Prometheus 호환을 학습합니다.

2026년 2월 14일·14분
인프라

7장: Collector 아키텍처와 배포

OTel Collector의 Receiver/Processor/Exporter 파이프라인, 핵심 프로세서 활용법, Kubernetes 환경에서의 DaemonSet/Deployment 배포를 학습합니다.

2026년 2월 20일·13분
이전 글4장: 메트릭(Metrics) 수집과 분석
다음 글6장: OTel SDK 계측 실전

댓글

목차

약 14분 남음
  • 학습 목표
  • OpenTelemetry의 로그 접근 방식
  • 로그 데이터 모델
  • 로그-트레이스 상관관계
    • 상관관계의 워크플로우
    • Python logging 브릿지
    • Go slog 브릿지
  • 구조화 로그 설계
    • 비구조화 vs 구조화 로그
    • 구조화 로그 작성 원칙
  • 로그 레벨 전략
    • 로그 레벨 정의
    • 환경별 로그 레벨 전략
    • Collector에서 로그 레벨 필터링
  • 로그 비용 관리
    • 로그 볼륨 줄이기
    • 로그 티어링
  • 정리