트레이스 브로드캐스팅
트레이스 브로드캐스팅
트레이스 브로드캐스팅(trace broadcasting)은 같은 OpenTelemetry 트레이스를 여러 목적지로 한 번에 보내는 방법이에요. 예를 들어, 장기 보관을 위한 자체 데이터 웨어하우스에 보내고, 동시에 LLM 관찰과 온라인 평가용으로 Confident AI에도 보낼 수 있죠.
Confident AI는 표준 OTLP/HTTP를 받아들이기 때문에, OTLP를 만들 수 있는 파이프라인이면 어떤 것이든 모든 트레이스의 사본을 https://otel.confident-ai.com/v1/traces로 보낼 수 있어요. 전용 프로토콜이나 래퍼 SDK는 필요 없어요.
출처: 문서
본문
개요
팀이 트레이스를 브로드캐스팅하는 흔한 이유는 다음과 같아요.
- 규정 준수 / 데이터 거주(residency) — 네트워크 밖으로 나가기 전에 모든 트레이스 사본을 내부 웨어하우스에 보관
- 벤더 독립성 — 나중에 옵저버빌리티 벤더를 바꾸거나 추가할 수 있도록 원시 스팬을 자체 인프라에 보관
- 전문 백엔드 — 서비스 모니터링에는 범용 APM(Datadog, Tempo, Jaeger)을, LLM 전용 평가에는 Confident AI를 사용
- 샘플링 분리 — 로컬 디버깅용으로는 100% 트레이스를 보관하고, 외부로는 샘플링된 일부만 전송
브로드캐스팅할 수 있는 동등한 방법이 두 가지 있어요. 트레이스를 퍼뜨리는 OpenTelemetry Collector(작은 독립형 바이너리)를 구성하거나, 애플리케이션 안에 exporter를 여러 개 직접 붙이면 돼요. 두 방법 모두 같은 결과를 만들어요.
Confident AI는 OTLP에 대해 gRPC를 지원하지 않고 HTTP만 지원해요. Collector에서는
otlphttp를, SDK에서는proto-http패키지의OTLPSpanExporter를 사용하세요.
아키텍처
Collector 경유
sequenceDiagram
participant App as Your Application
participant Collector as OpenTelemetry Collector
participant Warehouse as Data Warehouse
participant Confident as Confident AI
App->>Collector: Export OTLP spans
par Broadcast
Collector->>Warehouse: Send via OTLP / Kafka / file exporter
and
Collector->>Confident: Send via OTLP/HTTP<br/>(x-confident-api-key)
end
버퍼링, 재시도, 샘플링, PII 스크러빙이 모두 한 곳에 모이기 때문에 프로덕션에 권장돼요.
SDK 경유
sequenceDiagram
participant Code as Your Code
participant Provider as TracerProvider
participant ProcA as BatchSpanProcessor A
participant ProcB as BatchSpanProcessor B
participant Warehouse as Data Warehouse
participant Confident as Confident AI
Code->>Provider: span ends
par Independent export
Provider->>ProcA: onEnd(span)
ProcA->>Warehouse: OTLP export
and
Provider->>ProcB: onEnd(span)
ProcB->>Confident: OTLP/HTTP export
end
더 단순해서 단일 서비스 앱에 좋아요. 각 BatchSpanProcessor가 독립적으로 배칭·재시도하므로, 한 목적지에서 실패해도 다른 쪽은 영향받지 않아요.
설정
스택에 맞는 방식을 골라 쓰면 돼요 — 둘 다 같은 브로드캐스트를 달성합니다.
Collector (YAML)
receivers:
otlp:
protocols:
http:
grpc:
exporters:
otlphttp/warehouse:
endpoint: https://traces.internal.yourcompany.com
headers:
authorization: Bearer ${env:...KEY}
otlphttp/confident:
endpoint: https://otel.confident-ai.com
headers:
x-confident-api-key: ${env:...KEY}
service:
pipelines:
traces:
receivers: [otlp]
exporters: [otlphttp/warehouse, otlphttp/confident]
이 구성을 실행 중인 OpenTelemetry Collector에 로드하세요 — 배포 옵션은 공식 Collector 문서를 참고하세요. 같은 파이프라인에 exporter를 둘 다 나열하기만 하면 되고, 그러면 모든 스팬이 둘 다로 갑니다.
Python
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(
endpoint=f"{WAREHOUSE_ENDPOINT}/v1/traces",
headers={"authorization": f"Bearer {WAREHOUSE_API_KEY}"},
)))
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(
endpoint="https://otel.confident-ai.com/v1/traces",
headers={"x-confident-api-key": CONFIDENT_API_KEY},
)))
trace.set_tracer_provider(provider)
TypeScript
import { trace } from "@opentelemetry/api";
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
const provider = new NodeTracerProvider({
spanProcessors: [
new BatchSpanProcessor(new OTLPTraceExporter({
url: `${process.env.WAREHOUSE_ENDPOINT}/v1/traces`,
headers: { authorization: `Bearer ${process.env.WAREHOUSE_API_KEY}` },
})),
new BatchSpanProcessor(new OTLPTraceExporter({
url: "https://otel.confident-ai.com/v1/traces",
headers: { "x-confident-api-key": process.env.CONFIDENT_API_KEY ?? "" },
})),
],
});
trace.setGlobalTracerProvider(provider);
Go
warehouseExp, _ := otlptracehttp.New(ctx,
otlptracehttp.WithEndpoint(os.Getenv("WAREHOUSE_ENDPOINT")),
otlptracehttp.WithHeaders(map[string]string{
"authorization": "Bearer " + os.Getenv("WAREHOUSE_API_KEY"),
}),
)
confidentExp, _ := otlptracehttp.New(ctx,
otlptracehttp.WithEndpoint("otel.confident-ai.com"),
otlptracehttp.WithHeaders(map[string]string{
"x-confident-api-key": os.Getenv("CONFIDENT_API_KEY"),
}),
)
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(warehouseExp),
sdktrace.WithBatcher(confidentExp),
)
otel.SetTracerProvider(tp)
Java
OtlpHttpSpanExporter warehouse = OtlpHttpSpanExporter.builder()
.setEndpoint(System.getenv("WAREHOUSE_ENDPOINT") + "/v1/traces")
.addHeader("authorization", "Bearer " + System.getenv("WAREHOUSE_API_KEY"))
.build();
OtlpHttpSpanExporter confident = OtlpHttpSpanExporter.builder()
.setEndpoint("https://otel.confident-ai.com/v1/traces")
.addHeader("x-confident-api-key", System.getenv("CONFIDENT_API_KEY"))
.build();
SdkTracerProvider provider = SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(warehouse).build())
.addSpanProcessor(BatchSpanProcessor.builder(confident).build())
.build();
OpenTelemetrySdk.builder()
.setTracerProvider(provider)
.buildAndRegisterGlobal();
Ruby
require 'opentelemetry/sdk'
require 'opentelemetry/exporter/otlp'
OpenTelemetry::SDK.configure do |c|
c.add_span_processor(
OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
OpenTelemetry::Exporter::OTLP::Exporter.new(
endpoint: "#{ENV['WAREHOUSE_ENDPOINT']}/v1/traces",
headers: { 'authorization' => "Bearer #{ENV['WAREHOUSE_API_KEY']}" },
)
)
)
c.add_span_processor(
OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
OpenTelemetry::Exporter::OTLP::Exporter.new(
endpoint: 'https://otel.confident-ai.com/v1/traces',
headers: { 'x-confident-api-key' => ENV['CONFIDENT_API_KEY'] },
)
)
)
end
C#
using var provider = Sdk.CreateTracerProviderBuilder()
.AddSource("my-llm-app")
.AddOtlpExporter(o => {
o.Endpoint = new Uri($"{Environment.GetEnvironmentVariable("WAREHOUSE_ENDPOINT")}/v1/traces");
o.Headers = $"authorization=Bearer {Environment.GetEnvironmentVariable("WAREHOUSE_API_KEY")}";
o.Protocol = OtlpExportProtocol.HttpProtobuf;
})
.AddOtlpExporter(o => {
o.Endpoint = new Uri("https://otel.confident-ai.com/v1/traces");
o.Headers = $"x-confident-api-key={Environment.GetEnvironmentVariable("CONFIDENT_API_KEY")}";
o.Protocol = OtlpExportProtocol.HttpProtobuf;
})
.Build();
이후에는 평소처럼 스팬을 만들면 돼요 — 모든 스팬이 두 목적지로 흐릅니다.
고급 Collector 기능
이 기능들은 Collector 경로에서만 쓸 수 있어요. 애플리케이션 코드를 건드리지 않고 브로드캐스트 동작을 바꿀 수 있게 해 줍니다.
선택적 브로드캐스트
웨어하우스에는 100%를 보관하면서 LLM 태그가 붙은 스팬만 Confident AI로 보내려면 routing 커넥터를 사용하세요.
connectors:
routing:
default_pipelines: [traces/warehouse]
table:
- context: span
statement: route() where attributes["confident.span.type"] != nil
pipelines: [traces/warehouse, traces/confident]
샘플링
로컬에는 100%를 보관하되 Confident AI에는 10%만(플러스 모든 오류) 보내려면:
processors:
tail_sampling/confident:
decision_wait: 10s
policies:
- name: errors
type: status_code
status_code: { status_codes: [ERROR] }
- name: random
type: probabilistic
probabilistic: { sampling_percentage: 10 }
이걸 Confident AI 파이프라인에만 적용하고, 웨어하우스 파이프라인은 샘플링 없이 두세요.
PII 스크러빙
네트워크 밖으로 나가기 전에 민감 필드를 해시하거나 제거합니다.
processors:
attributes/redact:
actions:
- { key: user.email, action: hash }
- { key: http.request.header.authorization, action: delete }
그런 다음 파이프라인의 processors 목록에 attributes/redact를 추가하세요.
분산 추적과 결합하기
이미 여러 서비스에 걸쳐 분산 추적을 쓰고 있다면, 모든 서비스가 공유 Collector를 가리키게 하고 그 Collector가 브로드캐스트를 처리하게 하세요.
sequenceDiagram
participant ServiceA as Service A
participant ServiceB as Service B
participant ServiceC as Service C
participant Collector as OpenTelemetry Collector
participant Warehouse as Data Warehouse
participant Confident as Confident AI
ServiceA->>ServiceB: Request + traceparent
ServiceB->>ServiceC: Request + traceparent
par
ServiceA->>Collector: OTLP spans
and
ServiceB->>Collector: OTLP spans
and
ServiceC->>Collector: OTLP spans
end
par
Collector->>Warehouse: All spans
and
Collector->>Confident: All spans
end
traceparent가 종단 간 전파되기 때문에, 모든 목적지가 완전하고 통합된 트레이스를 받아요.
모든 서비스는 동일한
CONFIDENT_API_KEY를 사용해야 합니다. 키가 다르면 서로 다른 프로젝트로 라우팅되어 트레이스 통합이 깨져요.
모범 사례
Confident AI 속성 설정
브로드캐스팅은 스팬이 어디로 가는지만 바꾸지, 스팬이 무엇을 담는지는 바꾸지 않아요. 스팬은 여전히 confident.* 속성(예: confident.span.type, confident.span.input, confident.llm.model)을 담고 있어야 Observatory에서 제대로 렌더링됩니다. 스팬 수준 속성 매핑을 참고하세요.
진입 스팬에 confident.trace.user.id와 confident.trace.customer.id를 설정해 활동을 최종 사용자와 그 계정에 귀속시키세요. 아이덴티티 시맨틱은 사용자 및 고객 가이드를 참고하세요.
프로덕션에서는 Collector를 선호
서비스가 둘 이상이 되면 Collector를 권장해요.
- 단일 버퍼가 스파이크를 흡수해서, 모든 앱이 자체 큐를 들고 있을 필요가 없어요.
- 어느 목적지로의 네트워크 문제도 Collector에만 영향 — 앱은 쾌적하게 유지돼요.
- 앱 코드를 재배포하지 않고도 목적지, 샘플링, PII 규칙을 바꿀 수 있어요.
HTTP를 사용하고 gRPC는 피하기
Confident AI의 OTLP 엔드포인트는 HTTP만 받아들여요. Collector에서는 otlphttp를, SDK에서는 opentelemetry-exporter-otlp-proto-http의 OTLPSpanExporter를 사용하세요.
파이프라인별 환경 설정
OTEL_RESOURCE_ATTRIBUTES를 사용해 트레이스가 어느 Confident AI 환경에 들어갈지 제어하세요.
OTEL_RESOURCE_ATTRIBUTES="confident.trace.environment=production"
목적지별로 서로 다른 환경이 필요하면 서로 다른 리소스 프로세서를 가진 Collector 파이프라인 두 개를 돌리세요.
디버그 싱크는 분리해서
트레이스가 이상해 보이면 exporter를 한 번에 하나씩 꺼서, 문제가 상류인지 특정 목적지 때문인지 확인하세요.