OpenTelemetry instrumentation libraries
Send spans from FastAPI, SQLAlchemy, Celery and other OpenTelemetry instrumentation packages to Judgment.
Judgment didn’t write these packages. They all connect the same way, so this one page covers them.
Give the instrumentor Judgment’s provider
Tracer.init doesn’t install a global OpenTelemetry provider. An instrumentor that isn’t handed a provider uses the global one, which by default is a no-op, so its spans go nowhere. A plain FastAPIInstrumentor.instrument_app(app) after Tracer.init exports nothing.
Give the instrumentor Judgment’s provider in one of three ways, in this order of preference:
- Register it with Judgment.
Tracer.registerOTELInstrumentation(X())callsX().instrument(tracer_provider=<Judgment's provider>)for you. - Pass the provider yourself when the library’s API instruments one object at a time:
tracer_provider=JudgmentTracerProvider.get_instance(). - Install Judgment globally with
JudgmentTracerProvider.install_as_global_tracer_provider()when you can’t reach the call. Only the first global install wins; see Frameworks with built-in OpenTelemetry.
from fastapi import FastAPI
from judgeval import JudgmentTracerProvider, Tracer
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
tracer = Tracer.init(project_name="lunch-order-agent")
Tracer.registerOTELInstrumentation(HTTPXClientInstrumentor())
app = FastAPI()
FastAPIInstrumentor.instrument_app(
app, tracer_provider=JudgmentTracerProvider.get_instance()
)
@app.middleware("http")
async def activate_tracer(request, call_next):
tracer.set_active()
return await call_next(request)import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
import { JudgmentTracerProvider, Tracer } from "judgeval";
JudgmentTracerProvider.getInstance();
Tracer.registerOTELInstrumentation(new HttpInstrumentation());
export const tracerReady = Tracer.init({ projectName: "lunch-order-agent" });Register instrumentations in a CommonJS file your app loads before anything else. Instrumentations patch modules when they’re loaded, so a module your app imported earlier stays unpatched. JudgmentTracerProvider.getInstance() creates Judgment’s provider so the instrumentation can register before Tracer.init() finishes; without it, the SDK logs No active tracer provider. Instrumentation was not registered. See Set up tracing.
Web servers: activate the tracer per request
Judgment’s provider sends each span to the tracer that’s active in the current execution context. A web server may run requests in contexts that never saw Tracer.init. The judgeval repository’s own FastAPI example handles this with the middleware shown above: keep the object Tracer.init returns and call tracer.set_active() at the start of each request. In Flask, do the same in a @app.before_request function.
Library table
Install judgeval, the library, and the package in the second column. “Register” means Tracer.registerOTELInstrumentation(Class()).
| Library | Package | Class | How to wire it |
|---|---|---|---|
| FastAPI | opentelemetry-instrumentation-fastapi |
FastAPIInstrumentor |
Register before creating FastAPI(), or FastAPIInstrumentor.instrument_app(app, tracer_provider=...) |
| Starlette | opentelemetry-instrumentation-starlette |
StarletteInstrumentor |
StarletteInstrumentor.instrument_app(app, tracer_provider=...) |
| Flask | opentelemetry-instrumentation-flask |
FlaskInstrumentor |
Register before creating Flask(__name__), or FlaskInstrumentor().instrument_app(app, tracer_provider=...) |
| Django | opentelemetry-instrumentation-django |
DjangoInstrumentor |
Register in manage.py and in wsgi.py/asgi.py, before Django loads settings |
| aiohttp server | opentelemetry-instrumentation-aiohttp-server |
AioHttpServerInstrumentor |
Register |
| Any ASGI app | opentelemetry-instrumentation-asgi |
OpenTelemetryMiddleware |
app = OpenTelemetryMiddleware(app, tracer_provider=...) |
| Any WSGI app | opentelemetry-instrumentation-wsgi |
OpenTelemetryMiddleware |
app = OpenTelemetryMiddleware(app, tracer_provider=...) |
| httpx | opentelemetry-instrumentation-httpx |
HTTPXClientInstrumentor |
Register |
| requests | opentelemetry-instrumentation-requests |
RequestsInstrumentor |
Register |
| aiohttp client | opentelemetry-instrumentation-aiohttp-client |
AioHttpClientInstrumentor |
Register |
| SQLAlchemy | opentelemetry-instrumentation-sqlalchemy |
SQLAlchemyInstrumentor |
Register before create_engine, or SQLAlchemyInstrumentor().instrument(engine=engine, tracer_provider=...) |
| psycopg 3 | opentelemetry-instrumentation-psycopg |
PsycopgInstrumentor |
Register |
| asyncpg | opentelemetry-instrumentation-asyncpg |
AsyncPGInstrumentor |
Register |
| PyMongo | opentelemetry-instrumentation-pymongo |
PymongoInstrumentor |
Register |
| MySQL Connector | opentelemetry-instrumentation-mysql |
MySQLInstrumentor |
Register |
| sqlite3 | opentelemetry-instrumentation-sqlite3 |
SQLite3Instrumentor |
Register |
| Redis | opentelemetry-instrumentation-redis |
RedisInstrumentor |
Register |
| Celery | opentelemetry-instrumentation-celery |
CeleryInstrumentor |
Call Tracer.init and register inside a worker_process_init signal handler, so each worker process has its own tracer |
| AWS Lambda | opentelemetry-instrumentation-aws-lambda |
AwsLambdaInstrumentor |
Register at module level and call Tracer.force_flush() before the handler returns. See Tracing patterns |
Every tracer_provider=... above is JudgmentTracerProvider.get_instance(), imported with from judgeval import JudgmentTracerProvider. Module paths follow the package name: opentelemetry-instrumentation-httpx is from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor.
Celery, as a full example:
from celery import Celery
from celery.signals import worker_process_init
from judgeval import Tracer
from opentelemetry.instrumentation.celery import CeleryInstrumentor
app = Celery("lunch_orders", broker="redis://localhost:6379/0")
@worker_process_init.connect(weak=False)
def init_tracing(*args, **kwargs):
Tracer.init(project_name="lunch-order-agent")
Tracer.registerOTELInstrumentation(CeleryInstrumentor())
LLM instrumentation families
Three projects publish instrumentors for model providers and agent frameworks.
| Family | Packages | How it reaches Judgment | Mapping in Judgment |
|---|---|---|---|
| OpenInference (Arize) | openinference-instrumentation-<name>, module openinference.instrumentation.<name> |
Register | Built-in OpenInference mapper: span kind, input, output, model, tokens |
| OpenLLMetry (Traceloop) | opentelemetry-instrumentation-<name> (Python), @traceloop/instrumentation-<name> (TypeScript) |
Register | Built-in GenAI mapper: model, provider, tokens. Prompts written as indexed keys may not fill input and output |
| OpenLIT | openlit |
Openlit.initialize() from judgeval |
GenAI attributes, as above |
OpenLIT has a judgeval helper that passes Judgment’s tracer to openlit.init and turns off OpenLIT metrics:
from judgeval import Tracer
from judgeval.integrations.openlit import Openlit
Tracer.init(project_name="lunch-order-agent")
Openlit.initialize()
Import it from judgeval.integrations.openlit; from judgeval.integrations import Openlit raises ImportError. Extra keyword arguments go straight to openlit.init().
If a span arrives with the right model and tokens but empty input or output, write a mapper for the library’s keys. See Attribute keys and mappers.