OpenTelemetry Integration
OtelGuardrailHandler is a built-in GuardrailEventCallback that turns every guardrail check into an OpenTelemetry span, automatically nested under whatever span is active in your application.
Following OTel best practices — the library depends only on opentelemetry-api. Your application configures the SDK and chooses an exporter.
Install
pip install "mend-guardrails[observability]"
Quickstart
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
from opentelemetry.sdk.resources import Resource
# 1. Configure the OTel SDK once at application startup.
resource = Resource.create({"service.name": "my-guardrails-app"})
provider = TracerProvider(resource=resource)
provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)
# 2. Attach the handler to any Guardrails client.
from mendguardrails import MendGuardrailsAsyncOpenAI
from mendguardrails.observability.otel import OtelGuardrailHandler
client = MendGuardrailsAsyncOpenAI(
mend_key="...",
on_guardrail_event=OtelGuardrailHandler(),
)
OTLP exporter (production)
Export spans to any OpenTelemetry Collector, Jaeger, Datadog, etc.:
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
provider = TracerProvider(resource=Resource.create({"service.name": "my-app"}))
provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint="http://localhost:4317", insecure=True))
)
trace.set_tracer_provider(provider)
client = MendGuardrailsAsyncOpenAI(
mend_key="...",
on_guardrail_event=OtelGuardrailHandler(),
)
Span structure
Each span is named guardrail {guardrail_name}, kind INTERNAL, and automatically parented to whatever span is active on the calling thread — so guardrail spans appear nested inside your existing inference spans.
chat gpt-4o [CLIENT, 320 ms]
guardrail PromptInjection [INTERNAL, 4 ms]
gen_ai.operation.name = "guardrail.check"
gen_ai.provider.name = "mend"
gen_ai.request.model = "gpt-4o"
gen_ai.guardrail.name = "PromptInjection"
gen_ai.guardrail.type = "Prompt Injection"
gen_ai.guardrail.action = "block"
gen_ai.guardrail.direction = "input"
gen_ai.guardrail.severity = "high"
gen_ai.guardrail.detection = "Prompt injection detected"
gen_ai.guardrail.stage = "pre_flight"
gen_ai.guardrail.enforcement = True
gen_ai.guardrail.exec_failed = False
gen_ai.usage.input_tokens = 42 (when available)
Span status is ERROR only when the guardrail itself raised an unhandled exception (execution_failed=True). A block or alert is a successful security check — it does not mark the span as an error.
Prompt text capture (opt-in)
The evaluated prompt or response text is not attached to spans by default because many OTel backends are not PII-safe. Enable it explicitly when your backend is appropriately secured:
OtelGuardrailHandler(capture_prompt_text=True)
This sets the gen_ai.guardrail.checked_text attribute on the span.
Custom tracer
By default the handler obtains a tracer scoped to guardrails.observability.otel. Pass your own Tracer to override the instrumentation scope or use a non-global TracerProvider:
from opentelemetry import trace
app_tracer = trace.get_tracer("my-app")
OtelGuardrailHandler(tracer=app_tracer)
Compatible exporters
OtelGuardrailHandler works with the entire OTel ecosystem:
- Backends: Jaeger, Zipkin, AWS X-Ray, Google Cloud Trace, Azure Monitor
- Vendors: Datadog, New Relic, Honeycomb, Grafana Tempo, Lightstep
- Collector: OpenTelemetry Collector (OTLP)
Combining with other sinks
Pass a list to on_guardrail_event to run OTel alongside a custom sink concurrently:
client = MendGuardrailsAsyncOpenAI(
mend_key="...",
on_guardrail_event=[OtelGuardrailHandler(), my_database_sink],
)
See Observability for the full on_guardrail_event callback reference.