-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[Draft] Document OpenTelemetry integration #2001
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Add comprehensive documentation for integrating OpenTelemetry with FastMCP: - New integration guide at docs/integrations/opentelemetry.mdx - Covers logging integration with LoggingHandler - Demonstrates span creation via custom middleware - Includes production OTLP export configuration - Provides complete working example Also update related docs: - Add OpenTelemetry reference in middleware.mdx - Add tip about OpenTelemetry in logging.mdx - Register doc in docs.json under new Observability section Example code: - examples/opentelemetry_example.py with working weather server Closes #1998 Co-authored-by: William Easton <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering why we wouldn't just bake the instrumentation directly into FastMCP and enable it by default? It's not harmful to have the no-op/non-recording spans if you're not exporting them anywhere. There may be a very slight performance effect, but I think it's pretty minimal. Worst case, we could give people an option to disable it if they didn't want OTEL.
# Set up tracing | ||
trace_provider = TracerProvider(resource=resource) | ||
trace_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter())) | ||
trace.set_tracer_provider(trace_provider) | ||
|
||
# Set up logging | ||
logger_provider = LoggerProvider(resource=resource) | ||
logger_provider.add_log_record_processor(BatchLogRecordProcessor(ConsoleLogExporter())) | ||
set_logger_provider(logger_provider) | ||
|
||
# Attach OpenTelemetry to FastMCP's logger | ||
fastmcp_logger = get_logger("my_server") | ||
fastmcp_logger.addHandler(LoggingHandler(logger_provider=logger_provider)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this approach would be considered "manual" instrumentation, which is always an option, but wouldn't most folks want something like the "Zero Code" version: https://opentelemetry.io/docs/zero-code/python/
I think we should at least point folks to this as the default, and then we can explain in more detail the manual approach.
I do think there are some additional envvars or CLI switches required to get logging setup with the zero code approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain this a little more? What we would have to do to give folks the zero code experience?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh sure! The OTEL libraries (at least for Python, but I think for most languages) make a pretty strong distinction between producing telemetry (opentelemetry-api
) and exporting it (opentelemetry-sdk
) and they offer these zero-code approaches where you are essentially wrapping your entrypoint with opentelemetry-instrument
(in Python) so that your code doesn't need to set up any exporters. Their canonical example looks like this:
opentelemetry-instrument \
--traces_exporter console,otlp \
--metrics_exporter console \
--service_name your-service-name \
--exporter_otlp_endpoint 0.0.0.0:4317 \
python myapp.py
What would happen here is that your myapp.py
might be using opentelemetry-api
to make spans and metrics, and then when you run the program this way, the spans get exported to whichever ones you've configured there on the CLI. This example is very similar in spirit to what you laid out here, setting up OTLP exporters and attaching them to the providers, it's just handled by opentelemetry-instrument
.
So my proposal is just to point people to that link for how they'd run their fastmcp servers by, say, wrapping them like:
opentelemetry-instrument \
--traces_exporter console,otlp \
--metrics_exporter console \
--service_name my-mcp-server \
--exporter_otlp_endpoint 0.0.0.0:4317 \
fastmcp run mymcpserver.py
|
||
### Basic Tracing Middleware | ||
|
||
Create a middleware that emits spans for all MCP requests: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't we just ship this middleware with FastMCP? I'd argue it should be on by default, even.
@chrisguidry I had Claude generate this, I was mostly going to stick with logging for now I thought the problem with emitting spans was going to be that we don't have a way to accept or propagate the trace id through MCP calls so the spans would all be uncorrelated I'd be very happy to do more if we thought it was useful and sensible |
Yep, that's a great call, and I think it might need to be pushed lower into the base SDK? I would make an argument that even without trace propagation, there's still some value because you'd see all of the things your MCP server called out to, even if each tool call was a separate trace. Also, as soon as the community figures out client-side trace propagation, it would all "just work" (LOL). Didn't mean to block your efforts to get great documentation in for sure! |
I'm ok adding it to our client and server as experimental until the lower level sdk gets it together, would that be interesting for you? |
Add comprehensive documentation for integrating OpenTelemetry with FastMCP for distributed tracing and observability.
Changes
docs/integrations/opentelemetry.mdx
examples/opentelemetry_example.py
Closes #1998
Generated with Claude Code