Edictum
Go SDK

Go Observability

OpenTelemetry integration for Edictum Go -- governance spans, denial/allow metrics, and no-op defaults.

AI Assistance

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 overhead

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

AttributeTypeDescription
tool.namestringName of the tool being called
tool.side_effectstringSide effect classification (pure, read, write, irreversible)
tool.call_indexintSequential call index within the session
governance.environmentstringEnvironment name (e.g., "production")
governance.run_idstringUnique 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:

MetricTypeDescription
edictum.calls.deniedInt64CounterNumber of denied tool calls
edictum.calls.allowedInt64CounterNumber 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:

OptionDescription
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

On this page