Stamp trace_id and span_id on every log line inside a span
A processor in the shared chain reads the active span context and adds its IDs as lowercase hex, so both structlog and stdlib records carry them. The lowercase `trace_id` name is what the provisioned Loki derived field's case-sensitive regex matches; outside a span nothing is added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7be54a6ff9
commit
455b0970b1
@@ -2,6 +2,7 @@ import logging
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
|
from opentelemetry import trace
|
||||||
|
|
||||||
# Loggers that install their own handlers (uvicorn's dictConfig runs before the
|
# Loggers that install their own handlers (uvicorn's dictConfig runs before the
|
||||||
# app is imported). Their handlers are stripped so records propagate to the
|
# app is imported). Their handlers are stripped so records propagate to the
|
||||||
@@ -19,6 +20,22 @@ def _add_service(service_name: str):
|
|||||||
return processor
|
return processor
|
||||||
|
|
||||||
|
|
||||||
|
def _add_trace_context(logger, method_name, event_dict):
|
||||||
|
"""Stamp the active span's IDs on the record, as lowercase hex.
|
||||||
|
|
||||||
|
The name `trace_id` is load-bearing: Grafana's Loki derived field matches
|
||||||
|
the case-sensitive regex trace[_-]?id["=: ]+([a-fA-F0-9]+), so OTel's own
|
||||||
|
`otelTraceID` would never become a link. Outside a span (startup, the
|
||||||
|
consumers' idle loop) the context is invalid and nothing is added, rather
|
||||||
|
than an all-zeros ID that points at no trace.
|
||||||
|
"""
|
||||||
|
ctx = trace.get_current_span().get_span_context()
|
||||||
|
if ctx.is_valid:
|
||||||
|
event_dict["trace_id"] = trace.format_trace_id(ctx.trace_id)
|
||||||
|
event_dict["span_id"] = trace.format_span_id(ctx.span_id)
|
||||||
|
return event_dict
|
||||||
|
|
||||||
|
|
||||||
def configure_logging(service_name: str) -> structlog.stdlib.BoundLogger:
|
def configure_logging(service_name: str) -> structlog.stdlib.BoundLogger:
|
||||||
"""One JSON object per line on stdout, for structlog and stdlib loggers alike.
|
"""One JSON object per line on stdout, for structlog and stdlib loggers alike.
|
||||||
|
|
||||||
@@ -32,6 +49,7 @@ def configure_logging(service_name: str) -> structlog.stdlib.BoundLogger:
|
|||||||
structlog.stdlib.add_logger_name,
|
structlog.stdlib.add_logger_name,
|
||||||
structlog.processors.TimeStamper(fmt="iso", utc=True),
|
structlog.processors.TimeStamper(fmt="iso", utc=True),
|
||||||
_add_service(service_name),
|
_add_service(service_name),
|
||||||
|
_add_trace_context,
|
||||||
]
|
]
|
||||||
|
|
||||||
structlog.configure(
|
structlog.configure(
|
||||||
|
|||||||
Reference in New Issue
Block a user