In this guide, you’ll learn how to integrate OpenTelemetry into your Pyramid 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 pyramid sdk and necessary opentelemetry packages:
pip install monoscope-pyramid 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 python3 myapp.py
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 Pyramid Configuration
After setting up open telemetry, you can now configure and start the monoscope pyramid middleware.
Next, add the configuration variables to your settings or development.ini or production.ini file, like so:
settings = {
"MONOSCOPE_DEBUG": False,
"MONOSCOPE_TAGS": ["environment: production", "region: us-east-1"],
"MONOSCOPE_SERVICE_VERSION": "v2.0",
"MONOSCOPE_ROUTES_WHITELIST": ["/api/first", "/api/second"],
"MONOSCOPE_IGNORE_HTTP_CODES": [404, 429]
}
MONOSCOPE_DEBUG = False
MONOSCOPE_TAGS = environment: production, region: us-east-1
MONOSCOPE_SERVICE_VERSION = "v2.0"
MONOSCOPE_ROUTES_WHITELIST = /api/first, /api/second
MONOSCOPE_IGNORE_HTTP_CODES = 404, 429
Then, initialize Monoscope in your application’s entry point (e.g., app.py), like so:
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config
@view_config(route_name='home')
def home(request):
return Response('Welcome!')
if __name__ == '__main__':
settings = {
"MONOSCOPE_DEBUG": False,
}
with Configurator(settings=settings) as config:
# Initialize Monoscope
config.add_tween("monoscope_pyramid.Monoscope")
# END Initialize Monoscope
config.add_route('home', '/')
config.scan()
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()
| Option | Description |
|---|---|
MONOSCOPE_SERVICE_NAME |
A defined string name of your application |
MONOSCOPE_DEBUG |
Set to true to enable debug mode. |
MONOSCOPE_TAGS |
A list of defined tags for your services (used for grouping and filtering data on the dashboard). |
MONOSCOPE_SERVICE_VERSION |
A defined string version of your application (used for further debugging on the dashboard). |
MONOSCOPE_REDACT_HEADERS |
A list of HTTP header keys to redact. |
MONOSCOPE_REDACT_REQUEST_BODY |
A list of JSONPaths from the request body to redact. |
MONOSCOPE_REDACT_RESPONSE_BODY |
A list of JSONPaths from the response body to redact. |
MONOSCOPE_CAPTURE_REQUEST_BODY |
Set to true to capture the request body. |
MONOSCOPE_CAPTURE_RESPONSE_BODY |
Set to true to capture the response body. |
Tip
The {ENTER_YOUR_API_KEY_HERE} demo string should be replaced with the API key generated from the Monoscope dashboard.
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 your development.ini or production.ini file or settings with paths to the fields that should be redacted. There are three variables you can provide to configure what gets redacted, namely:
-
MONOSCOPE_REDACT_HEADERS: A list of HTTP header keys. -
MONOSCOPE_REDACT_REQUEST_BODY: A list of JSONPaths from the request body. -
MONOSCOPE_REDACT_RESPONSE_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:
settings = {
"MONOSCOPE_REDACT_HEADERS": ["content-type", "Authorization", "HOST"],
"MONOSCOPE_REDACT_REQUEST_BODY": ["$.user.email", "$.user.addresses"],
"MONOSCOPE_REDACT_RESPONSE_BODY": ["$.users[*].email", "$.users[*].credit_card"]
}
MONOSCOPE_REDACT_HEADERS: content-type, Authorization, HOST
MONOSCOPE_REDACT_REQ_BODY: $.user.email, $.user.addresses
MONOSCOPE_REDACT_RES_BODY: $.users[*].email, $.users[*].credit_card
Note
- The
MONOSCOPE_REDACT_HEADERSvariable expects a list of case-insensitive headers as strings. - The
MONOSCOPE_REDACT_REQ_BODYandMONOSCOPE_REDACT_RES_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
With Monoscope, you can track and report different unhandled or uncaught errors, API issues, and anomalies 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_pyramid module, passing in the request and error arguments, like so:
from pyramid.response import Response
from pyramid.view import view_config
from monoscope_pyramid import observe_request, report_error
@view_config(route_name='home')
def home(request):
try:
resp = observe_request(request).get(
"https://jsonplaceholder.typicode.com/todos/2")
return Response(resp.read())
except Exception as e:
report_error(request, e)
return Response("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 view (or a tween that runs after auth) — 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 pyramid.view import view_config
from monoscope_pyramid import set_user, set_tenant
@view_config(route_name="me", renderer="json")
def me_view(request):
user = request.authenticated_userid and request.user
if user is not None:
set_user({"id": user.id, "email": user.email, "name": user.full_name})
set_tenant({"id": user.org_id, "name": user.org_name})
return {"id": user.id}
Both helpers skip missing fields, so partial info is fine. Calls outside a Monoscope-handled request are silent no-ops.
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_pyramid module, passing in the request argument, like so:
from pyramid.response import Response
from pyramid.view import view_config
from monoscope_pyramid import observe_request
@view_config(route_name='home')
def home(request):
resp = observe_request(request).get(
"https://jsonplaceholder.typicode.com/todos/2")
return Response(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 Pyramid tween only covers HTTP requests. Celery tasks, RQ workers, APScheduler jobs, Pyramid console scripts, and standalone workers 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, Pyramid console scripts, 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 console scripts and other one-shot processes, 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 Pyramid SDK