OpenTelemetry & Structured Logging
Instrument agentguard with OpenTelemetry spans or the zero-dependency StructuredLogger.
Overview
The agentguard.telemetry module provides two complementary observability strategies:
- Built-in trace platform — SQLite persistence, CLI inspection, JSONL import/export, and a local dashboard
- OpenTelemetry tracing — emits spans and events to any OTel-compatible backend (Jaeger, Zipkin, Datadog, etc.)
- StructuredLogger — a zero-dependency JSON-lines logger that works everywhere
Recommended split: use the built-in SQLite trace platform for local operator workflows, JSONL for portable replay fixtures, and OpenTelemetry when traces need to join an existing platform-wide backend.
OpenTelemetry Integration
Call instrument_agentguard() once at startup to auto-instrument all guarded tools:
from agentguard.telemetry import instrument_agentguard
# Instrument all guarded tools
instrument_agentguard()
# Or target specific tools
instrument_agentguard(tool_names=["search_web", "query_db"])
Emitted Spans
| Span Name | Kind | Description |
|---|---|---|
agentguard.tool_call | Parent | Wraps the entire tool call lifecycle |
agentguard.guard.input_validation | Child | Input validation phase |
agentguard.guard.output_validation | Child | Output validation phase |
agentguard.guard.hallucination_check | Child | Hallucination detection (when enabled) |
agentguard.guard.circuit_breaker_check | Child | Circuit breaker state check |
agentguard.guard.rate_limit_check | Child | Rate limit check |
agentguard.guard.budget_check | Child | Budget enforcement check |
Span Attributes
The parent agentguard.tool_call span includes these attributes:
agentguard.tool.name— tool function nameagentguard.tool.status—success,failed,circuit_open, etc.agentguard.tool.execution_time_ms— wall-clock timeagentguard.tool.retry_count— number of retries
Events Emitted
agentguard.hallucination_detected— withconfidenceattributeagentguard.circuit_breaker_openedagentguard.budget_exceededagentguard.retry— withattemptattribute
Custom Spans
from agentguard.telemetry import guard_span
# Emit a custom span for code outside @guard
with guard_span("custom_check", tool_name="my_tool"):
perform_custom_check()
StructuredLogger
A zero-dependency alternative that writes JSON-lines to stdout or any file-like object:
from agentguard.telemetry import StructuredLogger
from agentguard import guard
logger = StructuredLogger(
include_args=False, # Don't log arguments (privacy)
buffer=True, # Keep records in memory
)
@guard(after_call=logger.after_call_hook)
def my_tool(x: str) -> str:
return x.upper()
my_tool("hello")
# Retrieve captured records
records = logger.get_records()
# [{{
# "event": "agentguard.tool_call",
# "tool_name": "my_tool",
# "status": "success",
# "execution_time_ms": 0.123,
# ...
# }}]
Manual Event Logging
logger = StructuredLogger()
# Log specific events
logger.log_hallucination("search_web", confidence=0.85, reason="Schema mismatch")
logger.log_circuit_breaker_opened("external_api")
logger.log_budget_exceeded("query_db", spent=5.25, limit=5.00)
logger.log_retry("unreliable_api", attempt=3)
# Generic event
logger.log_event("custom_event", tool_name="my_tool", severity="high")
Backend Integration
Configure your OTel exporter as usual — agentguard emits standard OTel spans:
# Jaeger
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(JaegerExporter()))
trace.set_tracer_provider(provider)
# Then instrument agentguard
from agentguard.telemetry import instrument_agentguard
instrument_agentguard()
Install OpenTelemetry support with: pip install awesome-agentguard[otel]. Without it, instrument_agentguard() still works but emits no-op spans.