Go Observability
OpenTelemetry integration for Edictum Go -- governance spans, denial/allow metrics, and no-op defaults.
Right page if: you want to instrument Edictum Go with OpenTelemetry for tracing and metrics on governance decisions. Wrong page if: you want Python observability -- see https://docs.edictum.ai/docs/reference/observability. For audit sinks (structured event logs), see https://docs.edictum.ai/docs/reference/audit-sinks. Gotcha: OTel is fully optional. When no SDK is installed, the global providers return no-op spans and counters -- zero overhead, zero configuration.
Edictum Go integrates with OpenTelemetry for governance tracing and metrics. Every guard.Run() call creates a span with tool call attributes, and denial/allow counters track enforcement decisions.
When no OTel SDK is installed, everything degrades to no-ops. There is no configuration required and no performance overhead.
Setup
Default (No-Op)
Out of the box, Edictum uses the OTel global providers. If you have not installed an OTel SDK, these return no-op implementations:
g, err := guard.FromYAML("contracts.yaml")
// OTel is active but producing no-ops -- zero overheadWith OTel SDK
Install and configure the OTel SDK, then pass custom providers to the guard:
import (
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/trace"
"github.com/edictum-ai/edictum-go/guard"
"github.com/edictum-ai/edictum-go/telemetry"
)
// Set up OTel SDK (exporter setup omitted for brevity)
tp := trace.NewTracerProvider(
trace.WithBatcher(traceExporter),
)
mp := metric.NewMeterProvider(
metric.WithReader(metric.NewPeriodicReader(metricExporter)),
)
g := guard.New(
guard.WithContracts(/* ... */),
guard.WithTracerProvider(tp),
guard.WithMeterProvider(mp),
)Or set the global providers and let Edictum pick them up automatically:
import "go.opentelemetry.io/otel"
otel.SetTracerProvider(tp)
otel.SetMeterProvider(mp)
// Edictum uses global providers by default
g, err := guard.FromYAML("contracts.yaml")Governance Spans
Every guard.Run() call creates a span under the edictum tracer with these attributes:
| Attribute | Type | Description |
|---|---|---|
tool.name | string | Name of the tool being called |
tool.side_effect | string | Side effect classification (pure, read, write, irreversible) |
tool.call_index | int | Sequential call index within the session |
governance.environment | string | Environment name (e.g., "production") |
governance.run_id | string | Unique ID for this pipeline run |
On denial, the span status is set to ERROR with the denial reason as the description.
Metrics
Two counters are registered under the edictum meter:
| Metric | Type | Description |
|---|---|---|
edictum.calls.denied | Int64Counter | Number of denied tool calls |
edictum.calls.allowed | Int64Counter | Number of allowed tool calls |
Both counters include a tool.name attribute for per-tool breakdowns.
Grafana Dashboard Example
# Denial rate by tool (last 5 minutes)
rate(edictum_calls_denied_total[5m])
# Allow/deny ratio
sum(rate(edictum_calls_allowed_total[5m]))
/
(sum(rate(edictum_calls_allowed_total[5m])) + sum(rate(edictum_calls_denied_total[5m])))Telemetry Options
The telemetry.New() function accepts these options:
| Option | Description |
|---|---|
telemetry.WithTracerProvider(tp) | Custom trace.TracerProvider (default: global) |
telemetry.WithMeterProvider(mp) | Custom metric.MeterProvider (default: global) |
When passing to the guard constructor, use the guard-level equivalents:
guard.New(
guard.WithTracerProvider(tp),
guard.WithMeterProvider(mp),
)Combining with Audit Sinks
OpenTelemetry and audit sinks serve different purposes:
- OTel spans/metrics: operational observability -- latency, error rates, dashboards, alerting
- Audit sinks: compliance trail -- what was called, what was denied, by whom, and why
Use both in production:
g, err := guard.FromYAML("contracts.yaml",
guard.WithAuditSink(fileAuditSink, serverAuditSink),
guard.WithTracerProvider(tp),
guard.WithMeterProvider(mp),
)Next Steps
Last updated on