OpenTelemetry & Structured Logging

Instrument agentguard with OpenTelemetry spans or the zero-dependency StructuredLogger.

Overview

The agentguard.telemetry module provides two complementary observability strategies:

  1. Built-in trace platform — SQLite persistence, CLI inspection, JSONL import/export, and a local dashboard
  2. OpenTelemetry tracing — emits spans and events to any OTel-compatible backend (Jaeger, Zipkin, Datadog, etc.)
  3. 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:

python
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 NameKindDescription
agentguard.tool_callParentWraps the entire tool call lifecycle
agentguard.guard.input_validationChildInput validation phase
agentguard.guard.output_validationChildOutput validation phase
agentguard.guard.hallucination_checkChildHallucination detection (when enabled)
agentguard.guard.circuit_breaker_checkChildCircuit breaker state check
agentguard.guard.rate_limit_checkChildRate limit check
agentguard.guard.budget_checkChildBudget enforcement check

Span Attributes

The parent agentguard.tool_call span includes these attributes:

Events Emitted

Custom Spans

python
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:

python
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

python
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:

python
# 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 OTel extras

Install OpenTelemetry support with: pip install awesome-agentguard[otel]. Without it, instrument_agentguard() still works but emits no-op spans.

Edit this page on GitHub