컨텍스트 전파: 로그, 추적 및 지표 상호 연관
La 컨텍스트 전파 모든 신호를 연결할 수 있는 메커니즘 서비스 수에 관계없이 요청을 처리하는 동안 생성된 원격 분석 십자가. 컨텍스트 전파가 없으면 추적, 로그 및 메트릭은 격리된 신호로 유지됩니다. 이를 통해 각 요청의 전체 스토리를 전달하는 일관된 내러티브가 됩니다.
이 기사에서는 OpenTelemetry의 컨텍스트 전파 메커니즘을 분석합니다. 추적 컨텍스트에서 수하물까지, 로그 추적 상관관계에서 패턴까지 서비스 경계를 넘어 신호가 손실되지 않습니다.
이 기사에서 배울 내용
- 서비스 간에 추적 컨텍스트 전파가 작동하는 방식
- W3C Baggage: 서비스 간 맞춤형 메타데이터 전달
- 로그-추적 상관관계: 구조화된 로그를 추적에 연결
- 전체 서비스 간 상관관계 패턴
- 비동기 시나리오(큐, 이벤트)에서 전파 관리
- 전파 문제 해결
추적 컨텍스트 전파
전파 컨텍스트 추적 그리고 이를 연결하는 기본 메커니즘은 동일한 추적에서 다양한 서비스 범위. 서비스가 다른 서비스를 호출할 때, 추적 컨텍스트(trace_id,span_id,trace_flags)는 요청 헤더에 직렬화됩니다. 하위 범위를 생성하기 위해 수신 서비스에 의해 역직렬화됩니다.
OpenTelemetry는 i를 사용합니다. 전파자 직렬화 및 역직렬화를 관리하기 위해 맥락의. 기본 전파자는 W3C TraceContext, 그러나 Otel은 지원합니다 B3(Zipkin), Jaeger 및 사용자 정의 형식도 있습니다.
from opentelemetry import trace, context
from opentelemetry.propagate import inject, extract
from opentelemetry.propagators.textmap import DefaultTextMapPropagator
import requests
tracer = trace.get_tracer("order-service")
# --- LATO CLIENT: iniettare il contesto nella richiesta ---
def call_payment_service(order):
with tracer.start_as_current_span("call-payment-service") as span:
span.set_attribute("order.id", order.id)
# Creare un dizionario per gli header
headers = {}
# Iniettare il trace context negli header
inject(headers)
# Ora headers contiene:
# { "traceparent": "00-4bf92f35...-00f067aa...-01",
# "tracestate": "" }
# Inviare la richiesta con gli header di contesto
response = requests.post(
"http://payment-service/api/charge",
json={"order_id": order.id, "amount": order.total},
headers=headers
)
return response.json()
# --- LATO SERVER: estrarre il contesto dalla richiesta ---
from flask import Flask, request as flask_request
app = Flask(__name__)
@app.route("/api/charge", methods=["POST"])
def handle_charge():
# Estrarre il trace context dagli header della richiesta
ctx = extract(flask_request.headers)
# Creare uno span figlio nel contesto estratto
with tracer.start_as_current_span(
"process-charge",
context=ctx,
kind=trace.SpanKind.SERVER,
attributes={
"payment.amount": flask_request.json["amount"]
}
) as span:
result = process_payment(flask_request.json)
span.set_attribute("payment.status", result.status)
return result.to_json()
W3C 수하물: 서비스 간 메타데이터
Il W3C 수하물 사용자 정의 키-값 쌍을 전달하는 메커니즘 추적 컨텍스트와 함께 서비스 경계를 넘나듭니다. 의 속성과 달리 범위(로컬)인 경우 수하물은 모든 다운스트림 서비스로 전파됩니다.
수하물은 범위에 속하지 않는 컨텍스트 정보를 운반하는 데 유용합니다. 고객 계층, A/B 테스트 변형, 요청 우선순위, 원산지.
from opentelemetry import baggage, context
from opentelemetry.propagate import inject
# --- Impostare il baggage nel servizio di origine ---
def handle_api_request(request):
# Impostare valori di baggage
ctx = baggage.set_baggage("customer.tier", "premium")
ctx = baggage.set_baggage("ab.test.variant", "checkout-v2", context=ctx)
ctx = baggage.set_baggage("request.priority", "high", context=ctx)
# Attaccare il contesto
token = context.attach(ctx)
try:
with tracer.start_as_current_span("handle-request") as span:
# Il baggage verrà propagato automaticamente
# a tutti i servizi downstream
headers = {}
inject(headers)
# headers ora contiene anche:
# "baggage": "customer.tier=premium,ab.test.variant=checkout-v2,request.priority=high"
call_downstream_service(headers)
finally:
context.detach(token)
# --- Leggere il baggage in un servizio downstream ---
def handle_downstream_request(request):
ctx = extract(request.headers)
token = context.attach(ctx)
try:
# Leggere i valori di baggage
customer_tier = baggage.get_baggage("customer.tier")
ab_variant = baggage.get_baggage("ab.test.variant")
priority = baggage.get_baggage("request.priority")
with tracer.start_as_current_span("downstream-operation") as span:
# Usare il baggage come attributi dello span
span.set_attribute("customer.tier", customer_tier or "standard")
span.set_attribute("ab.test.variant", ab_variant or "control")
# Differenziare il comportamento in base al baggage
if priority == "high":
process_with_priority(request)
else:
process_normal(request)
finally:
context.detach(token)
수하물: 언제 사용해야 하고 언제 피해야 할까요?
| 대본 | 추천 | 이유 |
|---|---|---|
| 라우팅을 위한 고객 계층 | 네, 수하물을 이용하세요 | 라우팅 결정을 위한 여러 서비스를 제공합니다. |
| A/B 테스트 변형 | 네, 수하물을 이용하세요 | 각 서비스는 어떤 변형을 제공해야 하는지 알아야 합니다. |
| 민감한 데이터(PII, 토큰) | 아니요, 수하물은 절대 안 돼요 | 수하물은 HTTP 헤더에서 암호화되지 않은 상태로 전달됩니다. |
| 빅데이터(페이로드, JSON) | 아니요, 피하세요 | 수하물은 각 요청의 크기를 증가시킵니다. |
| 요청 ID/상관 ID | 대신 Trace_id를 사용하세요. | Trace_id는 이미 추적 컨텍스트에 의해 전파되었습니다. |
로그-추적 상관관계
La 로그-추적 상관관계 그리고 그 동안 생성된 로그를 연결하는 패턴은
해당 트랙에 대한 요청을 처리합니다. 주입하여 얻습니다.
trace_id e span_id 각 로그 줄에서 필터링할 수 있습니다.
특정 추적과 관련된 모든 로그입니다.
import logging
import json
from opentelemetry import trace
class TraceContextFilter(logging.Filter):
"""Filtro che aggiunge trace_id e span_id a ogni record di log"""
def filter(self, record):
span = trace.get_current_span()
if span and span.is_recording():
ctx = span.get_span_context()
record.trace_id = format(ctx.trace_id, "032x")
record.span_id = format(ctx.span_id, "016x")
record.trace_flags = format(ctx.trace_flags, "02x")
record.service_name = "order-service"
else:
record.trace_id = "0" * 32
record.span_id = "0" * 16
record.trace_flags = "00"
record.service_name = "order-service"
return True
# Configurare il logger con il filtro
logger = logging.getLogger("order-service")
logger.addFilter(TraceContextFilter())
# Formatter JSON che include trace context
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter(json.dumps({
"timestamp": "%(asctime)s",
"level": "%(levelname)s",
"message": "%(message)s",
"service": "%(service_name)s",
"trace_id": "%(trace_id)s",
"span_id": "%(span_id)s",
"trace_flags": "%(trace_flags)s",
"logger": "%(name)s"
})))
logger.addHandler(handler)
# Uso: i log avranno automaticamente trace_id e span_id
def process_order(order):
with tracer.start_as_current_span("process-order") as span:
logger.info(f"Processing order {order.id}")
# Output: {"trace_id": "4bf92f35...", "span_id": "00f067aa...",
# "message": "Processing order ORD-123", ...}
validate_order(order)
logger.info(f"Order {order.id} validated successfully")
process_payment(order)
logger.info(f"Payment processed for order {order.id}")
비동기 시나리오의 전파
비동기 시나리오의 컨텍스트 전파(메시지 큐, 이벤트, 예약된 작업) 특별한 주의가 필요합니다. 헤더가 있는 동기 HTTP 호출과 달리 컨텍스트를 전달하려면 비동기 시스템에서 컨텍스트를 직렬화해야 합니다. 메시지 페이로드 또는 해당 메타데이터에 있습니다.
from opentelemetry import trace, context
from opentelemetry.propagate import inject, extract
import json
tracer = trace.get_tracer("order-service")
# --- PRODUCER: iniettare contesto nel messaggio Kafka ---
def publish_order_event(order, kafka_producer):
with tracer.start_as_current_span(
"publish-order-created",
kind=trace.SpanKind.PRODUCER,
attributes={
"messaging.system": "kafka",
"messaging.destination.name": "order-events",
"messaging.operation.type": "publish",
"order.id": order.id
}
) as span:
# Iniettare il trace context negli header del messaggio
headers = {}
inject(headers)
# Convertire gli header in formato Kafka
kafka_headers = [(k, v.encode()) for k, v in headers.items()]
kafka_producer.produce(
topic="order-events",
key=order.id.encode(),
value=json.dumps(order.to_dict()).encode(),
headers=kafka_headers
)
# --- CONSUMER: estrarre contesto dal messaggio Kafka ---
def consume_order_events(kafka_consumer):
for message in kafka_consumer:
# Convertire header Kafka in dizionario
headers = {k: v.decode() for k, v in message.headers()}
# Estrarre il trace context
ctx = extract(headers)
# Creare uno span nel contesto estratto
with tracer.start_as_current_span(
"process-order-event",
context=ctx,
kind=trace.SpanKind.CONSUMER,
attributes={
"messaging.system": "kafka",
"messaging.destination.name": "order-events",
"messaging.operation.type": "process"
}
) as span:
order_data = json.loads(message.value())
span.set_attribute("order.id", order_data["id"])
process_order_event(order_data)
컨텍스트 전파를 위한 체크리스트
- 동기 HTTP: 컨텍스트는 HTTP 헤더를 통한 자동 계측에 의해 자동으로 전파됩니다.
- gRPC: gRPC(자동 계측) 메타데이터를 통한 자동 전파
- 메시지 대기열: 메시지 헤더에 컨텍스트를 수동으로 삽입/추출합니다.
- 스레드 풀: 다음을 사용하여 새 스레드에서 컨텍스트를 캡처하고 다시 연결합니다.
attach/detach - 비동기/대기: 비동기 프레임워크가 컨텍스트를 유지하는지 확인합니다(Python asyncio는 기본적으로 이를 지원합니다).
- 예약된 작업: 새로운 루트 스팬을 생성하거나 원본 트랙에 연결
전파 문제 해결
전파 문제는 관찰 가능성을 채택하는 데 가장 일반적인 문제 중 하나입니다. 때 트랙이 "깨진" 것으로 나타납니다(상위 요소가 없는 격리된 범위). 일반적으로 문제가 있습니다. 컨텍스트 전파. 가장 자주 발생하는 원인과 해결 방법은 다음과 같습니다.
일반적인 전파 문제
깨진 트랙: 스팬은 연결된 트랙이 아닌 별도의 트랙으로 나타납니다. 원인:
중간 프록시 또는 로드 밸런서가 헤더를 제거합니다. traceparent. 해결 방법: 구성
추적 헤더를 전달하는 프록시입니다.
스레드에서 컨텍스트 손실: 하위 스레드에서 생성된 범위에는 상위가 없습니다.
원인: 컨텍스트가 스레드 경계를 넘어 전파되지 않습니다. 해결책: 사용
context.get_current() e attach() 새 스레드에서.
전파자가 구성되지 않았습니다.: 서비스는 다양한 형식(W3C vs B3)을 사용합니다.
해결 방법: 구성 CompositePropagator 필요한 모든 형식이 포함되어 있습니다.
수하물이 전파되지 않음: 수하물 가치는 다운스트림 서비스에 도달하지 않습니다.
원인: BaggagePropagator 등록되지 않았습니다. 해결책:
W3CBaggagePropagator 전파자의 구성에.
결론 및 다음 단계
컨텍스트 전파는 전체 관찰 시스템을 하나로 묶는 접착제입니다. 이것이 없으면 추적, 로그 및 메트릭은 격리된 신호로 유지됩니다. 적절한 전파를 통해, 진입 시점부터 모든 요청을 따를 수 있게 해주는 통합된 내러티브가 됩니다. 관련된 모든 서비스를 통해 응답까지.
세 가지 주요 메커니즘은 다음과 같습니다. W3C TraceContext 전파를 위해 추적_ID, W3C 수하물 사용자 정의 메타데이터를 전달하기 위한 로그-추적 상관관계 로그를 해당 추적에 연결합니다.
다음 기사에서는eBPF 계측, 혁명적인 기술 애플리케이션 코드를 수정하지 않고도 커널 수준에서 관찰 가능성을 얻을 수 있습니다. 에이전트 없는 시스템 호출을 가로채기 위해 eBPF 프로그램을 사용합니다.







