Skip to content

Commit b8d4f34

Browse files
authored
Merge pull request #76 from golf-mcp/asch/platform-telemetry
Asch/platform telemetry
2 parents cde5cc0 + 995bf09 commit b8d4f34

File tree

4 files changed

+606
-55
lines changed

4 files changed

+606
-55
lines changed

src/golf/core/builder.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,6 @@ def _generate_server(self) -> None:
561561
# Add OpenTelemetry imports if enabled
562562
if self.settings.opentelemetry_enabled:
563563
imports.extend(generate_telemetry_imports())
564-
imports.append("")
565564

566565
# Add imports section for different transport methods
567566
if self.settings.transport == "sse" or self.settings.transport in [
@@ -664,7 +663,10 @@ def _generate_server(self) -> None:
664663
# Add code to register this component
665664
if self.settings.opentelemetry_enabled:
666665
# Use telemetry instrumentation
667-
registration = f"# Register the {component_type.value} '{component.name}' with telemetry"
666+
registration = (
667+
f"# Register the {component_type.value} "
668+
f"'{component.name}' with telemetry"
669+
)
668670
entry_func = (
669671
component.entry_function
670672
if hasattr(component, "entry_function")
@@ -673,18 +675,31 @@ def _generate_server(self) -> None:
673675
)
674676

675677
# Debug: Add logging to verify wrapping
676-
registration += f"\n_wrapped_func = instrument_{component_type.value}({full_module_path}.{entry_func}, '{component.name}')"
678+
registration += (
679+
f"\\n_wrapped_func = instrument_{component_type.value}("
680+
f"{full_module_path}.{entry_func}, '{component.name}')"
681+
)
677682

678683
if component_type == ComponentType.TOOL:
679-
registration += f'\nmcp.add_tool(_wrapped_func, name="{component.name}", description="{component.docstring or ""}"'
684+
registration += (
685+
f'\\nmcp.add_tool(_wrapped_func, name="{component.name}", '
686+
f'description="{component.docstring or ""}"'
687+
)
680688
# Add annotations if present
681689
if hasattr(component, "annotations") and component.annotations:
682690
registration += f", annotations={component.annotations}"
683691
registration += ")"
684692
elif component_type == ComponentType.RESOURCE:
685-
registration += f'\nmcp.add_resource_fn(_wrapped_func, uri="{component.uri_template}", name="{component.name}", description="{component.docstring or ""}")'
693+
registration += (
694+
f"\\nmcp.add_resource_fn(_wrapped_func, "
695+
f'uri="{component.uri_template}", name="{component.name}", '
696+
f'description="{component.docstring or ""}")'
697+
)
686698
else: # PROMPT
687-
registration += f'\nmcp.add_prompt(_wrapped_func, name="{component.name}", description="{component.docstring or ""}")'
699+
registration += (
700+
f'\\nmcp.add_prompt(_wrapped_func, name="{component.name}", '
701+
f'description="{component.docstring or ""}")'
702+
)
688703
else:
689704
# Standard registration without telemetry
690705
if component_type == ComponentType.TOOL:
@@ -803,6 +818,18 @@ def _generate_server(self) -> None:
803818
server_code_lines.append(mcp_instance_line)
804819
server_code_lines.append("")
805820

821+
# Add early telemetry initialization if enabled (before component registration)
822+
early_telemetry_init = []
823+
if self.settings.opentelemetry_enabled:
824+
early_telemetry_init.extend(
825+
[
826+
"# Initialize telemetry early to ensure instrumentation works",
827+
"from golf.telemetry.instrumentation import init_telemetry",
828+
f'init_telemetry("{self.settings.name}")',
829+
"",
830+
]
831+
)
832+
806833
# Main entry point with transport-specific app initialization
807834
main_code = [
808835
'if __name__ == "__main__":',
@@ -917,12 +944,13 @@ def _generate_server(self) -> None:
917944

918945
# Combine all sections
919946
# Order: imports, env_section, auth_setup, server_code (mcp init),
920-
# post_init (API key middleware), component_registrations, main_code (run block)
947+
# early_telemetry_init, component_registrations, health_check_code, main_code (run block)
921948
code = "\n".join(
922949
imports
923950
+ env_section
924951
+ auth_setup_code
925952
+ server_code_lines
953+
+ early_telemetry_init
926954
+ component_registrations
927955
+ health_check_code
928956
+ main_code

src/golf/telemetry/instrumentation.py

Lines changed: 26 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ def init_telemetry(service_name: str = "golf-mcp-server") -> TracerProvider | No
2929
"""
3030
global _provider
3131

32+
# Check for Golf platform integration first
33+
golf_api_key = os.environ.get("GOLF_API_KEY")
34+
if golf_api_key and not os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT"):
35+
# Auto-configure for Golf platform
36+
os.environ["OTEL_TRACES_EXPORTER"] = "otlp_http"
37+
os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = "http://localhost:8000/api/v1/otel"
38+
os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"X-Golf-Key={golf_api_key}"
39+
print("[INFO] Auto-configured OpenTelemetry for Golf platform ingestion")
40+
3241
# Check for required environment variables based on exporter type
3342
exporter_type = os.environ.get("OTEL_TRACES_EXPORTER", "console").lower()
3443

@@ -37,7 +46,8 @@ def init_telemetry(service_name: str = "golf-mcp-server") -> TracerProvider | No
3746
endpoint = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT")
3847
if not endpoint:
3948
print(
40-
"[WARNING] OpenTelemetry tracing is disabled: OTEL_EXPORTER_OTLP_ENDPOINT is not set for OTLP HTTP exporter"
49+
"[WARNING] OpenTelemetry tracing is disabled: "
50+
"OTEL_EXPORTER_OTLP_ENDPOINT is not set for OTLP HTTP exporter"
4151
)
4252
return None
4353

@@ -47,6 +57,14 @@ def init_telemetry(service_name: str = "golf-mcp-server") -> TracerProvider | No
4757
"service.version": os.environ.get("SERVICE_VERSION", "1.0.0"),
4858
"service.instance.id": os.environ.get("SERVICE_INSTANCE_ID", "default"),
4959
}
60+
61+
# Add Golf-specific attributes if available
62+
if golf_api_key:
63+
golf_server_id = os.environ.get("GOLF_SERVER_ID")
64+
if golf_server_id:
65+
resource_attributes["golf.server.id"] = golf_server_id
66+
resource_attributes["golf.platform.enabled"] = "true"
67+
5068
resource = Resource.create(resource_attributes)
5169

5270
# Create provider
@@ -71,6 +89,10 @@ def init_telemetry(service_name: str = "golf-mcp-server") -> TracerProvider | No
7189
exporter = OTLPSpanExporter(
7290
endpoint=endpoint, headers=header_dict if header_dict else None
7391
)
92+
93+
# Log successful configuration for Golf platform
94+
if golf_api_key:
95+
print(f"[INFO] OpenTelemetry configured for Golf platform: {endpoint}")
7496
else:
7597
# Default to console exporter
7698
exporter = ConsoleSpanExporter(out=sys.stderr)
@@ -113,21 +135,6 @@ def init_telemetry(service_name: str = "golf-mcp-server") -> TracerProvider | No
113135
traceback.print_exc()
114136
raise
115137

116-
# Create a test span to verify everything is working
117-
try:
118-
test_tracer = provider.get_tracer("golf.telemetry.test", "1.0.0")
119-
with test_tracer.start_as_current_span("startup.test") as span:
120-
span.set_attribute("test", True)
121-
span.set_attribute("service.name", service_name)
122-
span.set_attribute("exporter.type", exporter_type)
123-
span.set_attribute(
124-
"endpoint", os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT", "not set")
125-
)
126-
except Exception:
127-
import traceback
128-
129-
traceback.print_exc()
130-
131138
return provider
132139

133140

@@ -154,15 +161,8 @@ def instrument_tool(func: Callable[..., T], tool_name: str) -> Callable[..., T]:
154161

155162
tracer = get_tracer()
156163

157-
# Add debug logging
158-
print(
159-
f"[TELEMETRY DEBUG] Instrumenting tool: {tool_name} (function: {func.__name__})"
160-
)
161-
162164
@functools.wraps(func)
163165
async def async_wrapper(*args, **kwargs):
164-
print(f"[TELEMETRY DEBUG] Executing async tool: {tool_name}")
165-
166166
# Create a more descriptive span name
167167
span_name = f"mcp.tool.{tool_name}.execute"
168168

@@ -247,10 +247,6 @@ async def async_wrapper(*args, **kwargs):
247247
# Add event for tool execution start
248248
span.add_event("tool.execution.started", {"tool.name": tool_name})
249249

250-
print(
251-
f"[TELEMETRY DEBUG] Tool span created: {span_name} (span_id: {span.get_span_context().span_id:016x})"
252-
)
253-
254250
try:
255251
result = await func(*args, **kwargs)
256252
span.set_status(Status(StatusCode.OK))
@@ -288,9 +284,6 @@ async def async_wrapper(*args, **kwargs):
288284
# For any result, record its type
289285
span.set_attribute("mcp.tool.result.class", type(result).__name__)
290286

291-
print(
292-
f"[TELEMETRY DEBUG] Tool execution completed successfully: {tool_name}"
293-
)
294287
return result
295288
except Exception as e:
296289
span.record_exception(e)
@@ -305,13 +298,10 @@ async def async_wrapper(*args, **kwargs):
305298
"error.message": str(e),
306299
},
307300
)
308-
print(f"[TELEMETRY DEBUG] Tool execution failed: {tool_name} - {e}")
309301
raise
310302

311303
@functools.wraps(func)
312304
def sync_wrapper(*args, **kwargs):
313-
print(f"[TELEMETRY DEBUG] Executing sync tool: {tool_name}")
314-
315305
# Create a more descriptive span name
316306
span_name = f"mcp.tool.{tool_name}.execute"
317307

@@ -396,10 +386,6 @@ def sync_wrapper(*args, **kwargs):
396386
# Add event for tool execution start
397387
span.add_event("tool.execution.started", {"tool.name": tool_name})
398388

399-
print(
400-
f"[TELEMETRY DEBUG] Tool span created: {span_name} (span_id: {span.get_span_context().span_id:016x})"
401-
)
402-
403389
try:
404390
result = func(*args, **kwargs)
405391
span.set_status(Status(StatusCode.OK))
@@ -437,9 +423,6 @@ def sync_wrapper(*args, **kwargs):
437423
# For any result, record its type
438424
span.set_attribute("mcp.tool.result.class", type(result).__name__)
439425

440-
print(
441-
f"[TELEMETRY DEBUG] Tool execution completed successfully: {tool_name}"
442-
)
443426
return result
444427
except Exception as e:
445428
span.record_exception(e)
@@ -454,7 +437,6 @@ def sync_wrapper(*args, **kwargs):
454437
"error.message": str(e),
455438
},
456439
)
457-
print(f"[TELEMETRY DEBUG] Tool execution failed: {tool_name} - {e}")
458440
raise
459441

460442
# Return appropriate wrapper based on function type
@@ -1004,16 +986,13 @@ async def dispatch(self, request: Request, call_next):
1004986
app = getattr(mcp_instance, "app", getattr(mcp_instance, "_app", None))
1005987
if app and hasattr(app, "add_middleware"):
1006988
app.add_middleware(SessionTracingMiddleware)
1007-
print("[TELEMETRY DEBUG] Added SessionTracingMiddleware to FastMCP app")
1008989

1009990
# Also try to instrument FastMCP's internal handlers
1010991
if hasattr(mcp_instance, "_tool_manager") and hasattr(
1011992
mcp_instance._tool_manager, "tools"
1012993
):
1013-
print(
1014-
f"[TELEMETRY DEBUG] Found {len(mcp_instance._tool_manager.tools)} tools in FastMCP"
1015-
)
1016994
# The tools should already be instrumented when they were registered
995+
pass
1017996

1018997
# Try to patch FastMCP's request handling to ensure context propagation
1019998
if hasattr(mcp_instance, "handle_request"):
@@ -1026,10 +1005,9 @@ async def traced_handle_request(*args, **kwargs):
10261005
return await original_handle_request(*args, **kwargs)
10271006

10281007
mcp_instance.handle_request = traced_handle_request
1029-
print("[TELEMETRY DEBUG] Patched FastMCP handle_request method")
10301008

1031-
except Exception as e:
1032-
print(f"[TELEMETRY DEBUG] Error setting up telemetry middleware: {e}")
1009+
except Exception:
1010+
# Silently continue if middleware setup fails
10331011
import traceback
10341012

10351013
traceback.print_exc()

0 commit comments

Comments
 (0)