1+ import httpx
12from common .core .logging import setup_logging
23from common .core .otel import (
34 add_otel_trace_context ,
45 build_otel_log_provider ,
56 build_tracer_provider ,
67 make_structlog_otel_processor ,
78)
8- from opentelemetry import trace
9+ from opentelemetry import baggage , trace
10+ from opentelemetry .baggage .propagation import W3CBaggagePropagator
11+ from opentelemetry .context import Context
12+ from opentelemetry .sdk .trace import ReadableSpan , Span , SpanProcessor , TracerProvider
913from structlog .typing import Processor
1014
11- from flagsmith_mcp import config
15+ from flagsmith_mcp import config , constants
16+ from flagsmith_mcp .events import get_client_info
1217
1318APPLICATION_LOGGERS = ["flagsmith_mcp" , "fastmcp" , "mcp" ]
1419
1520
21+ class ClientInfoSpanProcessor (SpanProcessor ):
22+ """Annotate started spans with this service's identity and the MCP
23+ client identity."""
24+
25+ def on_start (self , span : Span , parent_context : Context | None = None ) -> None :
26+ span .set_attribute ("flagsmith.client.name" , constants .FLAGSMITH_CLIENT_NAME )
27+ if (client_info := get_client_info ()) is not None :
28+ span .set_attribute ("flagsmith.mcp.client.name" , client_info .name )
29+ span .set_attribute ("flagsmith.mcp.client.version" , client_info .version )
30+
31+
32+ async def propagate_span_attributes (request : httpx .Request ) -> None :
33+ span = trace .get_current_span ()
34+ if not isinstance (span , ReadableSpan ):
35+ return
36+ ctx : Context | None = None
37+ for key , value in (span .attributes or {}).items ():
38+ ctx = baggage .set_baggage (key , str (value ), context = ctx )
39+ if ctx is not None :
40+ W3CBaggagePropagator ().inject (request .headers , context = ctx )
41+
42+
1643def setup_telemetry (settings : config .Settings ) -> None :
1744 """Set up logging, exporting structlog events and traces to OpenTelemetry
1845 when an OTLP endpoint is configured."""
@@ -27,14 +54,16 @@ def setup_telemetry(settings: config.Settings) -> None:
2754 add_otel_trace_context ,
2855 make_structlog_otel_processor (log_provider ),
2956 ]
30- # Setting a global tracer provider also activates FastMCP's built-in
31- # per-request server spans.
32- trace .set_tracer_provider (
33- build_tracer_provider (
34- endpoint = f"{ endpoint } /v1/traces" ,
35- service_name = settings .otel_service_name ,
36- )
57+ tracer_provider = build_tracer_provider (
58+ endpoint = f"{ endpoint } /v1/traces" ,
59+ service_name = settings .otel_service_name ,
3760 )
61+ else :
62+ # No exporter: spans stay in-process, but still feed the API
63+ # baggage propagation.
64+ tracer_provider = TracerProvider ()
65+ tracer_provider .add_span_processor (ClientInfoSpanProcessor ())
66+ trace .set_tracer_provider (tracer_provider )
3867 setup_logging (
3968 log_level = settings .log_level ,
4069 log_format = settings .log_format ,
0 commit comments