From f4694a99d64d71e1be37da3640990493cdbcb1d5 Mon Sep 17 00:00:00 2001 From: strawgate Date: Mon, 13 Apr 2026 01:26:29 -0500 Subject: [PATCH] fix: add is_recording() guards and use __qualname__ for error.type Wrap attribute-setting blocks in server_span, delegate_span, and client_span with `if span.is_recording():` to avoid unnecessary work on non-recording spans. Use `type(e).__qualname__` instead of `type(e).__name__` for the error.type attribute so nested/inner exception classes get their fully qualified name. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/fastmcp/client/telemetry.py | 44 ++++++++++--------- src/fastmcp/server/telemetry.py | 76 +++++++++++++++++---------------- 2 files changed, 63 insertions(+), 57 deletions(-) diff --git a/src/fastmcp/client/telemetry.py b/src/fastmcp/client/telemetry.py index bcbcf7aee..a5a419154 100644 --- a/src/fastmcp/client/telemetry.py +++ b/src/fastmcp/client/telemetry.py @@ -24,30 +24,32 @@ def client_span( """ tracer = get_tracer() with tracer.start_as_current_span(name, kind=SpanKind.CLIENT) as span: - attrs: dict[str, str] = { - # MCP semantic conventions - "mcp.method.name": method, - # FastMCP-specific attributes - "fastmcp.component.key": component_key, - } - if session_id is not None: - attrs["mcp.session.id"] = session_id - if resource_uri: - attrs["mcp.resource.uri"] = resource_uri - if tool_name is not None: - attrs["gen_ai.tool.name"] = tool_name - if prompt_name is not None: - attrs["gen_ai.prompt.name"] = prompt_name - span.set_attributes(attrs) + if span.is_recording(): + attrs: dict[str, str] = { + # MCP semantic conventions + "mcp.method.name": method, + # FastMCP-specific attributes + "fastmcp.component.key": component_key, + } + if session_id is not None: + attrs["mcp.session.id"] = session_id + if resource_uri: + attrs["mcp.resource.uri"] = resource_uri + if tool_name is not None: + attrs["gen_ai.tool.name"] = tool_name + if prompt_name is not None: + attrs["gen_ai.prompt.name"] = prompt_name + span.set_attributes(attrs) try: yield span except Exception as e: - span.set_attribute( - "error.type", - "tool_error" - if type(e).__name__ == "ToolError" - else type(e).__name__, - ) + if span.is_recording(): + span.set_attribute( + "error.type", + "tool_error" + if type(e).__qualname__ == "ToolError" + else type(e).__qualname__, + ) span.record_exception(e) span.set_status(Status(StatusCode.ERROR, str(e))) raise diff --git a/src/fastmcp/server/telemetry.py b/src/fastmcp/server/telemetry.py index fa687553c..e7eb84d20 100644 --- a/src/fastmcp/server/telemetry.py +++ b/src/fastmcp/server/telemetry.py @@ -73,32 +73,34 @@ def server_span( context=_get_parent_trace_context(), kind=SpanKind.SERVER, ) as span: - attrs: dict[str, str] = { - # MCP semantic conventions - "mcp.method.name": method, - # FastMCP-specific attributes - "fastmcp.server.name": server_name, - "fastmcp.component.type": component_type, - "fastmcp.component.key": component_key, - **get_auth_span_attributes(), - **get_session_span_attributes(), - } - if resource_uri is not None: - attrs["mcp.resource.uri"] = resource_uri - if tool_name is not None: - attrs["gen_ai.tool.name"] = tool_name - if prompt_name is not None: - attrs["gen_ai.prompt.name"] = prompt_name - span.set_attributes(attrs) + if span.is_recording(): + attrs: dict[str, str] = { + # MCP semantic conventions + "mcp.method.name": method, + # FastMCP-specific attributes + "fastmcp.server.name": server_name, + "fastmcp.component.type": component_type, + "fastmcp.component.key": component_key, + **get_auth_span_attributes(), + **get_session_span_attributes(), + } + if resource_uri is not None: + attrs["mcp.resource.uri"] = resource_uri + if tool_name is not None: + attrs["gen_ai.tool.name"] = tool_name + if prompt_name is not None: + attrs["gen_ai.prompt.name"] = prompt_name + span.set_attributes(attrs) try: yield span except Exception as e: - span.set_attribute( - "error.type", - "tool_error" - if type(e).__name__ == "ToolError" - else type(e).__name__, - ) + if span.is_recording(): + span.set_attribute( + "error.type", + "tool_error" + if type(e).__qualname__ == "ToolError" + else type(e).__qualname__, + ) span.record_exception(e) span.set_status(Status(StatusCode.ERROR, str(e))) raise @@ -118,22 +120,24 @@ def delegate_span( """ tracer = get_tracer() with tracer.start_as_current_span(f"delegate {name}") as span: - attrs: dict[str, str] = { - "fastmcp.provider.type": provider_type, - "fastmcp.component.key": component_key, - } - if method is not None: - attrs["mcp.method.name"] = method - span.set_attributes(attrs) + if span.is_recording(): + attrs: dict[str, str] = { + "fastmcp.provider.type": provider_type, + "fastmcp.component.key": component_key, + } + if method is not None: + attrs["mcp.method.name"] = method + span.set_attributes(attrs) try: yield span except Exception as e: - span.set_attribute( - "error.type", - "tool_error" - if type(e).__name__ == "ToolError" - else type(e).__name__, - ) + if span.is_recording(): + span.set_attribute( + "error.type", + "tool_error" + if type(e).__qualname__ == "ToolError" + else type(e).__qualname__, + ) span.record_exception(e) span.set_status(Status(StatusCode.ERROR, str(e))) raise