FastAPI SDK Guide
In this guide, you’ll learn how to integrate OpenTelemetry into your FastAPI application and install the Monoscope SDK to enhance its functionalities. By combining OpenTelemetry’s robust tracing and metrics capabilities with the Monoscope SDK, you’ll be able to monitor incoming and outgoing requests, report errors, and gain deeper insights into your application’s performance. This setup provides comprehensive observability, helping you track requests and troubleshoot issues effectively.
Prerequisites
Ensure you have already completed the first three steps of the onboarding guide.
Install via Claude Code
Use Claude Code? Our skill will instrument this project for you. Add the marketplace, install the skill, and install our CLI:
claude plugin marketplace add monoscope-tech/skills
claude plugin install monoscope-skills@monoscope-skills
curl monoscope.tech/install.sh | sh
monoscope auth login
Then run inside Claude Code:
/monoscope-skills:instrument OpenTelemetry via Monoscope into this project
The skill drives the CLI to wire up the SDK and verify it. Prefer a human? Email us — happy to jump on a call or connect over Slack.
Installation
Kindly run the command below to install the monoscope fastapi sdk and necessary opentelemetry packages:
pip install monoscope-fastapi opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install
Setup Open Telemetry
Setting up open telemetry allows you to send traces, metrics and logs to the Monoscope platform. To setup open telemetry, you need to configure the following environment variables:
export OTEL_EXPORTER_OTLP_ENDPOINT="http://otelcol.monoscope.tech:4317"
export OTEL_SERVICE_NAME="my-service" # Specifies the name of the service.
export OTEL_RESOURCE_ATTRIBUTES="x-api-key={ENTER_YOUR_API_KEY_HERE}" # Adds your API KEY to the resource.
export OTEL_EXPORTER_OTLP_PROTOCOL="grpc" #Specifies the protocol to use for the OpenTelemetry exporter.
export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true # enable logging auto instrumentation
Then run the command below to start your server with opentelemetry instrumented:
opentelemetry-instrument uvicorn main:app
Or run using gunicorn with gunicorn config
opentelemetry-instrument gunicorn -c gunicorn.conf.py main:app
Tip
The {ENTER_YOUR_API_KEY_HERE} demo string should be replaced with the API key generated from the Monoscope dashboard.
Import / load order matters: opentelemetry-instrument reads the OTEL_* environment variables when it starts. Export them in the same shell (or load them from a .env file) before invoking the command, otherwise the instrumentation will use defaults and your traces won't reach Monoscope.
Monoscope FastAPI Configuration
After setting up open telemetry, you can now configure the monoscope fastapi middleware.
Next, initialize Monoscope in your application’s entry point (e.g., main.py), like so:
from fastapi import FastAPI
from monoscope_fastapi import Monoscope
app = FastAPI()
# Initialize Monoscope
monoscope = Monoscope()
app.middleware("http")(monoscope.middleware)
# END Initialize Monoscope
@app.get("/")
def read_root():
return {"Hello": "World"}
| Option | Description |
|---|---|
service_name |
A defined string name of your application (used for further debugging on the dashboard). |
debug |
Set to true to enable debug mode. |
tags |
A list of defined tags for your services (used for grouping and filtering data on the dashboard). |
service_version |
A defined string version of your application (used for further debugging on the dashboard). |
redact_headers |
A list of HTTP header keys to redact. |
redact_response_body |
A list of JSONPaths from the request body to redact. |
redact_request_body |
A list of JSONPaths from the response body to redact. |
capture_request_body |
Set to true to capture the request body. |
capture_response_body |
Set to true to capture the response body. |
Redacting Sensitive Data
If you have fields that are sensitive and should not be sent to Monoscope servers, you can mark those fields to be redacted (the fields will never leave your servers).
To mark a field for redacting via this SDK, you need to add some additional fields to the monoscope configuration object with paths to the fields that should be redacted. There are three variables you can provide to configure what gets redacted, namely:
-
redact_headers: A list of HTTP header keys. -
redact_response_body: A list of JSONPaths from the request body. -
redact_request_body: A list of JSONPaths from the response body.
JSONPath is a query language used to select and extract data from JSON files. For example, given the following sample user data JSON object:
{
"user": {
"name": "John Martha",
"email": "[email protected]",
"addresses": [
{
"street": "123 Main St",
"city": "Anytown",
"state": "CA",
"zip": "12345"
},
{
"street": "123 Main St",
"city": "Anytown",
"state": "CA",
"zip": "12345"
}
],
"credit_card": {
"number": "4111111111111111",
"expiration": "12/28",
"cvv": "123"
}
}
}
Examples of valid JSONPath expressions would be:
| JSONPath | Description |
|---|---|
$.user.addresses[*].zip |
In this case, Monoscope will replace the zip field in all the objects of the addresses list inside the user object with the string [CLIENT_REDACTED]. |
$.user.credit_card |
In this case, Monoscope will replace the entire credit_card object inside the user object with the string [CLIENT_REDACTED]. |
Tip
To learn more about JSONPaths, please take a look at the official docs or use this JSONPath Evaluator to validate your JSONPath expressions.
You can also use our JSON Redaction Tool to preview what the final data sent from your API to Monoscope will look like, after redacting any given JSON object.
Here’s an example of what the configuration would look like with redacted fields:
from fastapi import FastAPI
from monoscope_fastapi import Monoscope
app = FastAPI()
redact_headers = ["content-type", "Authorization", "HOST"]
redact_response_body = ["$.user.email", "$.user.addresses"]
redact_request_body = ["$.users[*].email", "$.users[*].credit_card"]
monoscope = Monoscope(
redact_headers=redact_headers,
redact_response_body=redact_response_body,
redact_request_body=redact_request_body
)
app.middleware("http")(monoscope.middleware)
@app.get("/")
def read_root():
return {"Hello": "World"}
Note
- The
redact_headersvariable expects a list of case-insensitive headers as strings. - The
redact_response_bodyandredact_request_bodyvariables expect a list of JSONPaths as strings. - The list of items to be redacted will be applied to all endpoint requests and responses on your server.
Error Reporting
Monoscope automatically detects different unhandled errors, API issues, and anomalies but you can report and track specific errors at different parts of your application. This will help you associate more detail and context from your backend with any failing customer request.
To manually report specific errors at different parts of your application, use the report_error() function from the monoscope_fastapi module, passing in the request and error, like so:
from fastapi import FastAPI, Request
from monoscope_fastapi import Monoscope, report_error
app = FastAPI()
monoscope = Monoscope()
app.middleware("http")(monoscope.middleware)
@app.get('/')
async def sample_route(request: Request):
try:
v = 1 / 0
return {"zero_division": v}
except Exception as e:
# Report the error to Monoscope
report_error(request, e)
return {"message": "Something went wrong"}
Identifying users & tenants
Attach the authenticated user and tenant to every request span so you can filter, group, and search by identity in the dashboard (e.g. “all errors for user.email = [email protected]”). Call set_user and set_tenant from a dependency that runs after auth (or directly inside the route handler) — the SDK writes them to the active request span using the standard attribute keys (user.id, user.email, user.full_name, tenant.id, tenant.name).
from fastapi import Depends, FastAPI
from monoscope_fastapi import set_user, set_tenant
async def attach_identity(user = Depends(get_current_user)):
set_user({"id": user.id, "email": user.email, "name": user.full_name})
set_tenant({"id": user.org_id, "name": user.org_name})
return user
@app.get("/me")
async def me(user = Depends(attach_identity)):
return {"id": user.id}
Both helpers skip missing fields, so partial info is fine. They must run inside a request handled by the Monoscope middleware — calls outside that scope are silent no-ops. Because identity lives in a ContextVar, calling set_user from async code is safe across awaits.
Monitoring HTTPX requests
Outgoing requests are external API calls you make from your API. By default, Monoscope monitors all requests users make from your application and they will all appear in the API Log Explorer page. However, you can separate outgoing requests from others and explore them in the Outgoing Integrations page, alongside the incoming request that triggered them.
To monitor outgoing HTTP requests from your application, use the observe_request() function from the monoscope_fastapi module, passing in the request argument, like so:
from fastapi import FastAPI, Request
from monoscope_fastapi import observe_request
app = FastAPI()
monoscope = Monoscope()
app.middleware("http")(monoscope.middleware)
@app.get('/')
async def sample_route(request: Request):
resp = observe_request(request).get("https://jsonplaceholder.typicode.com/todos/2")
return resp.read()
The observe_request() function accepts a required request argument, and the following optional arguments:
| Option | Description |
|---|---|
url_wildcard |
The url_path string for URLs with path parameters. |
redact_headers |
A list of HTTP header keys to redact. |
redact_response_body |
A list of JSONPaths from the request body to redact. |
redact_request_body |
A list of JSONPaths from the response body to redact. |
Non-HTTP Entry Points (Background Jobs, Workers, CLIs)
The FastAPI middleware only covers HTTP requests. Celery tasks, RQ workers, APScheduler jobs, Dramatiq actors, and standalone scripts are invisible until you instrument them yourself. Always cover these alongside your HTTP routes — without it, half your production work has no observability.
For Celery, install the auto-instrumentation package and every task automatically gets a span:
pip install opentelemetry-instrumentation-celery
from celery.signals import worker_process_init
from opentelemetry.instrumentation.celery import CeleryInstrumentor
@worker_process_init.connect(weak=False)
def init_celery_tracing(*args, **kwargs):
CeleryInstrumentor().instrument()
For RQ, APScheduler, FastAPI BackgroundTasks, raw threads, or any custom worker, wrap each handler in a span with the OpenTelemetry tracer API:
from opentelemetry import trace
from opentelemetry.trace.status import Status, StatusCode
tracer = trace.get_tracer("my-service-worker")
def process_email(payload: dict) -> None:
with tracer.start_as_current_span(
"email.send",
attributes={
"messaging.system": "rq",
"messaging.operation": "process",
"messaging.destination.name": "emails",
"code.function": "process_email",
},
) as span:
try:
send_email(payload)
except Exception as exc:
span.record_exception(exc)
span.set_status(Status(StatusCode.ERROR))
raise
For one-shot scripts, call trace.get_tracer_provider().shutdown() before exiting so the BatchSpanProcessor flushes; otherwise spans are dropped silently.
Tip
The `observe_request()` function wraps an HTTPX client and you can use it just like you would normally use HTTPX for any request.
Explore the FastAPI SDK