---
sidebar_label: "분산 추적 통합"
title: "분산 추적 통합 - CyberGo DD | Context 와 추적 가이드"
description: "CyberGo DD 분산 추적 통합 가이드입니다. TraceID, SpanID, RequestID 컨텍스트 전파, ContextExtractor 커스텀 추출기, HTTP 미들웨어 통합, 요청 스코프 로깅과 OpenTelemetry 분산 추적 시스템 통합을 다룹니다."
sidebar_position: 1
---

# 분산 추적 통합

DD 는 `context.Context` 기반의 추적 식별자 도구 함수 (`WithTraceID`/`GetTraceID` 등) 를 제공하여 마이크로서비스 아키텍처에서 로그를 연관 짓기 편리합니다. 주의: **DD 의 로그 메서드는 `context.Context` 매개변수를 받지 않으므로** 요청 스코프에서 TraceID 를 자동으로 추출할 수 없습니다 - `WithFields()`로 추적 식별자를 필드로 수동 추가해야 합니다 ([HTTP 미들웨어 통합](#http-미들웨어-통합) 참조).

## 컨텍스트 키

DD 는 세 가지 컨텍스트 키를 미리 정의합니다.

| 키 | 설명 | 용도 |
|-----|------|------|
| `ContextKeyTraceID` | 추적 ID | 서비스 간 추적, 전체 요청 체인 연관 |
| `ContextKeySpanID` | Span ID | 서비스 내 작업 추적 |
| `ContextKeyRequestID` | 요청 ID | 단일 요청 고유 식별자 |

## 기본 사용

### 설정과 가져오기

```go
ctx := context.Background()

// 추적 식별자 설정
ctx = dd.WithTraceID(ctx, "trace-abc123")
ctx = dd.WithSpanID(ctx, "span-def456")
ctx = dd.WithRequestID(ctx, "req-789")

// 추적 식별자 가져오기
traceID := dd.GetTraceID(ctx)    // "trace-abc123"
spanID := dd.GetSpanID(ctx)      // "span-def456"
requestID := dd.GetRequestID(ctx) // "req-789"
```

### 로그에 '자동 추출'이 불가능한 이유

:::warning 경고 현재 제한
DD 의 로그 메서드 (`Info`, `InfoWith` 등) 는 `context.Context` 매개변수를 직접 받지 않습니다. 컨텍스트 추출기는 내부적으로 `context.Background()`로 호출 (`logger.go:1414`) 되므로 요청 스코프의 context 에서 TraceID 등의 값을 직접 가져올 수 없습니다. 수동으로 필드를 전달하는 방식을 권장합니다 (아래 HTTP 미들웨어 통합 참조).
:::

```go
// 컨텍스트 추출기는 구성에 미리 설정된 정적 컨텍스트 필드에 사용됩니다
// 주의: 로그 메서드가 context 를 받지 않으므로 추출기의 GetTraceID 등 함수는
// 요청 스코프의 context 값을 가져올 수 없습니다

// 권장 방식: WithFields 로 추적 필드를 수동 전달
reqLog := logger.WithFields(
    dd.String("trace_id", traceID),
    dd.String("request_id", requestID),
)
reqLog.Info("요청 처리")
```

## HTTP 미들웨어 통합

### 기본 추적 미들웨어

```go
func TracingMiddleware(logger *dd.Logger) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // 요청 헤더에서 추적 식별자 추출 또는 생성
            traceID := r.Header.Get("X-Trace-ID")
            if traceID == "" {
                traceID = uuid.New().String()
            }

            requestID := r.Header.Get("X-Request-ID")
            if requestID == "" {
                requestID = uuid.New().String()
            }

            // context 에 주입
            ctx := r.Context()
            ctx = dd.WithTraceID(ctx, traceID)
            ctx = dd.WithRequestID(ctx, requestID)

            // 요청 스코프 로그 Entry 생성
            reqLog := logger.WithFields(
                dd.String("trace_id", traceID),
                dd.String("request_id", requestID),
            )

            // Logger 를 핸들러에 전달 (충돌 회피용 커스텀 타입 키 사용)
            type ctxKey struct{}
            ctx = context.WithValue(ctx, ctxKey{}, reqLog)
            next.ServeHTTP(w, r.WithContext(ctx))

            reqLog.InfoWith("요청 완료",
                dd.String("method", r.Method),
                dd.String("path", r.URL.Path),
            )
        })
    }
}
```

### 완전한 요청 추적 예

<!-- check-code: skip -->
```go
package main

import (
    "net/http"

    "github.com/cybergodev/dd"
)

type Handler struct {
    log *dd.LoggerEntry
}

func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()

    // context 에서 추적 정보 가져오기
    traceID := dd.GetTraceID(ctx)
    reqID := dd.GetRequestID(ctx)

    h.log.InfoWith("사용자 조회",
        dd.String("trace_id", traceID),
        dd.String("request_id", reqID),
        dd.String("user_id", r.PathValue("id")),
    )

    // 비즈니스 로직...

    h.log.InfoWith("조회 완료",
        dd.String("trace_id", traceID),
        dd.Int("status", 200),
    )
}
```

## ContextExtractor 커스텀 추출기

`ContextExtractor`는 context 에서 필드를 추출하는 데 사용됩니다. 주의: 로그 메서드가 context 매개변수를 받지 않으므로 추출기는 내부적으로 `context.Background()`로 호출되며 다음 시나리오에 적합합니다.

- 글로벌 context 또는 goroutine-local 저장소에서 정적 필드 추출
- HTTP 미들웨어와 결합해 추적 필드를 수동으로 `WithFields`에 전달

### 커스텀 추출기 예

```go
// 커스텀 추출기: 매 로그에 정적/글로벌 메타데이터 추가
func tenantExtractor(ctx context.Context) []dd.Field {
    return []dd.Field{
        dd.String("service", "order-service"),
        dd.String("env", os.Getenv("APP_ENV")),
    }
}

// 추출기 등록
logger.AddContextExtractor(tenantExtractor)
```

:::warning 경고 컨텍스트 제한
`ContextExtractor` 함수가 받는 것은 요청 스코프의 context 가 아닌 `context.Background()`입니다. 매 요청의 추적 ID 를 추가하려면 위의 `WithFields()` 패턴으로 요청 스코프의 `LoggerEntry`를 생성하세요.
:::

### 여러 추출기 조합

```go
// 여러 추출기를 등록하여 서로 다른 글로벌 메타데이터 수집
logger.AddContextExtractor(func(ctx context.Context) []dd.Field {
    return []dd.Field{
        dd.String("hostname", getHostname()),
        dd.String("version", buildVersion),
    }
})

logger.AddContextExtractor(tenantExtractor)
```

## 마이크로서비스 간 전파

마이크로서비스 호출에서 추적 식별자는 HTTP 헤더로 전파됩니다.

```go
// 발신 측: 추적 식별자를 요청 헤더에 주입
func callUpstream(ctx context.Context, url string) (*http.Response, error) {
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }

    // 추적 식별자 전파
    if traceID := dd.GetTraceID(ctx); traceID != "" {
        req.Header.Set("X-Trace-ID", traceID)
    }
    if reqID := dd.GetRequestID(ctx); reqID != "" {
        req.Header.Set("X-Request-ID", reqID)
    }

    return http.DefaultClient.Do(req)
}
```

## 요청 스코프 로그 패턴

```go
type RequestLogger struct {
    log    *dd.LoggerEntry
    ctx    context.Context
    start  time.Time
}

func NewRequestLogger(logger *dd.Logger, r *http.Request) *RequestLogger {
    ctx := r.Context()
    ctx = dd.WithTraceID(ctx, r.Header.Get("X-Trace-ID"))
    ctx = dd.WithRequestID(ctx, r.Header.Get("X-Request-ID"))

    return &RequestLogger{
        log: logger.WithFields(
            dd.String("trace_id", dd.GetTraceID(ctx)),
            dd.String("request_id", dd.GetRequestID(ctx)),
            dd.String("method", r.Method),
            dd.String("path", r.URL.Path),
        ),
        ctx:   ctx,
        start: time.Now(),
    }
}

func (rl *RequestLogger) Info(msg string, fields ...dd.Field) {
    rl.log.InfoWith(msg, fields...)
}

func (rl *RequestLogger) Finish(status int) {
    rl.log.InfoWith("요청 완료",
        dd.Int("status", status),
        dd.Duration("elapsed", time.Since(rl.start)),
    )
}
```

## 다음 단계

- [훅 시스템](../operations/hooks) -- 라이프사이클 훅 확장
- [감사 로그](../security/audit-logging) -- 보안 감사
- [API 레퍼런스 - Context](../../api-reference/output-integration/context) -- Context 완전한 API
- [웹 서비스 예제](../../examples/web-service) -- 완전한 웹 서비스 예
