metry

package module
v0.1.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 15, 2026 License: MIT Imports: 23 Imported by: 0

README

metry

Go OpenTelemetry OpenLLMetry

Universal, zero-boilerplate OpenTelemetry & LLMOps hub for Go AI applications. One line of code to trace them all.


Why metry

  • Zero-Boilerplate Init — Configure Tracer, Meter, and W3C propagators in a single call. No OTel SDK setup boilerplate.
  • 100% Vendor-Agnostic (OTLP First) — Works out of the box with Jaeger, Grafana Tempo, Langfuse, Phoenix, Datadog. Swap the backend by changing one line.
  • OpenLLMetry Semantic Conventions — Built-in typed constants and helpers for token usage, cost, and prompts (gen_ai.system, gen_ai.usage, etc.). Future-proof design allows transparent migration to official OTel GenAI semconv when they mature.
  • Plug-and-Play Middlewares — Ready-made wrappers for net/http and gRPC to create root spans and propagate trace_id.

Architecture

graph LR
  subgraph Libraries
    ragy[ragy]
    flowy[flowy]
    toolsy[toolsy]
    app[your app]
  end

  subgraph metryPkg [metry]
    metryCore[Init - global OTel providers]
    metryGenAI[genai]
    metryHTTP[HTTP middleware]
    metryGRPC[gRPC middleware]
  end

  subgraph Backends
    langfuse[Langfuse]
    jaeger[Jaeger]
    tempo[Grafana Tempo]
  end

  ragy --> metryCore
  flowy --> metryCore
  toolsy --> metryCore
  app --> metryCore
  app --> metryGenAI
  app --> metryHTTP
  app --> metryGRPC
  metryCore --> langfuse
  metryCore --> jaeger
  metryCore --> tempo

Installation

go get github.com/skosovsky/metry

Quick Start

package main

import (
	"context"
	"log"
	"net/http"

	"github.com/skosovsky/metry"
	metryhttp "github.com/skosovsky/metry/middleware/http"
)

func main() {
	ctx := context.Background()

	shutdown, err := metry.Init(ctx,
		metry.WithServiceName("my-ai-service"),
		metry.WithEnvironment("production"),
		metry.WithOTLPGRPC("localhost:4317", true),
		metry.WithTraceRatio(1.0),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer shutdown(ctx)

	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	})
	handler := metryhttp.Handler(mux, "HTTP /")
	log.Fatal(http.ListenAndServe(":8080", handler))
}

Semantic Conventions (LLMOps)

This version of metry does not provide GlobalTracer() or GlobalMeter(). Use otel.Tracer("your-service/module") and otel.Meter("your-service/module") for instrumentation scope so dashboards can filter traces and metrics by module.

Record token usage and cost on the current span so backends like Langfuse or Phoenix can show agent trees and costs. When you call metry.Init with a metric exporter, GenAI counters (tokens, cost, TTFT) are registered automatically.

import (
	"github.com/skosovsky/metry/genai"
	"go.opentelemetry.io/otel"
)

// Use a tracer named after your service/module for granular filtering in Jaeger/Grafana:
ctx, span := otel.Tracer("my-ai-service/llm").Start(ctx, "llm-call")
defer span.End()

// After the LLM responds:
genai.RecordUsage(ctx, span, 150, 50, 0.002)  // input tokens, output tokens, cost USD
genai.RecordInteraction(span, "Summarize this", "Here is the summary...")

Use otel.Tracer("your-service/module") (not a global tracer) so dashboards can filter by instrumentation scope. Spans tagged with gen_ai.usage.* and gen_ai.prompt / gen_ai.completion are recognized by OpenLLMetry-compatible backends.

To record failures on a span in a consistent way (e.g. in HTTP/gRPC handlers or after a failed LLM call), use traceutil.SpanError so the span gets the error recorded and status set to Error:

import "github.com/skosovsky/metry/traceutil"

// In a handler, before span.End():
if err != nil {
	traceutil.SpanError(span, err)
}
defer span.End()

Long prompt/completion/tool strings are truncated to 16 KB by default (UTF-8-safe, result length always ≤ limit); you can set a different limit with metry.WithMaxGenAIContextLength(bytes) in Init. Tool spans use otel.Tracer("metry/genai") at runtime so genai stays independent of the metry package. The library follows a clean-break policy: initialization is via metry.Init only (no genai.Init or legacy options). Before calling metry.Init again, call the returned shutdown so metrics can be re-registered; the test suite validates the lifecycle Init -> shutdown -> Init.

Agentic & RAG Tracing

Each tool call gets its own child span so parallel tool invocations have correct timing in Jaeger/Tempo. Start a tool span before execution and record the result on that span; use events for agent steps so ReAct loops (Thought -> Action -> Observation) appear as a chronological list:

// Start a child span for the tool (caller MUST call span.End(), e.g. via defer):
ctx, span := genai.StartToolSpan(ctx, "search", "call-1", `{"q":"weather"}`)
defer span.End()

// After the tool returns (result or error), record on the same span:
genai.RecordToolResult(span, `{"temp":22}`, false)

// After checking semantic cache in RAG layer:
genai.RecordCacheHit(span, true, "pgvector_cache")

// When transitioning workflow steps (e.g. in flowy); each call adds an event (no overwrite).
// Event name gen_ai.agent.step follows OTel GenAI semantic conventions.
genai.RecordAgentStep(span, "cardiologist", "specialist", "step-2")

Streaming & UX Metrics

Record Time To First Token (TTFT) for streaming LLM responses. Pass the model name so dashboards can show latency per LLM (e.g. gpt-4o vs claude-3-5):

start := time.Now()
// ... start streaming, receive first token ...
genai.RecordTTFT(ctx, time.Since(start).Seconds(), "gpt-4o")

The gen_ai.client.ttft histogram (unit: seconds) is exported with a gen_ai.request.model dimension when you call metry.Init with a metric exporter.

Context Propagation (Baggage)

Propagate key-value metadata (e.g. session_id, patient_id) across HTTP and gRPC boundaries. Keys and values must comply with W3C Baggage; invalid key/value returns a wrapped error.

// At entry point (e.g. after auth):
ctx, err := metry.SetBaggageValue(ctx, "patient_id", "p-123")
if err != nil {
	// invalid key/value (e.g. spaces, special chars)
}

// Downstream (any service receiving the context):
id := metry.BaggageValue(ctx, "patient_id") // "p-123"

Security Observability

Use the security package to record security interventions (e.g. PII masking, LLM judges, shadow mode) as span events and to tag spans with ai.security.* attributes for dashboards.

import "github.com/skosovsky/metry/security"

// Record an intervention as an event on the current span (e.g. from middleware):
security.RecordSecurityEvent(ctx, security.ActionBlock, "pii_masking", "PII detected in prompt", false)

// Tag the whole security pipeline span (e.g. for Grafana):
span.SetAttributes(
	security.ShadowModeKey.Bool(true),
	security.ValidatorKey.String("llm_judge"),
	security.ActionKey.String(security.ActionPass),
)
Attribute Description
ai.security.tier Protection tier (e.g. 1, 2, 3).
ai.security.validator Name of the filter (e.g. pii_masking, llm_judge, vector_firewall).
ai.security.action Decision: pass, block, redact. Use security.ActionPass, security.ActionBlock, security.ActionRedact.
ai.security.shadow_mode If true, blocking was virtual (shadow mode).
ai.security.score Confidence or cosine distance for semantic checks.
ai.security.reason Human-readable reason for block or mutation.

To separate guard-evaluation cost from user-facing generation in billing and dashboards, record usage with an explicit purpose so it is written to both span attributes and metric data points. Use genai.RecordUsage (defaults to genai.PurposeGeneration) for normal user-facing calls, and genai.RecordUsageWithPurpose(..., genai.PurposeGuardEvaluation) for LLM-judge or other guard calls:

// Normal reply to the user (purpose = "generation"):
genai.RecordUsage(ctx, span, 150, 50, 0.002)

// LLM-judge / guard evaluation (purpose = "guard_evaluation") — same metrics, split by purpose:
genai.RecordUsageWithPurpose(ctx, span, 20, 5, 0.0003, genai.PurposeGuardEvaluation)

HTTP and gRPC

  • HTTP — Wrap your handler: metryhttp.Handler(mux, "operation-name").
  • gRPC — Use metrygrpc.ServerOptions() when creating the server and metrygrpc.ClientDialOption() when creating the client.

Ecosystem

metry is the central observability layer for the AI stack. It makes libraries such as ragy (RAG), flowy (orchestration), and toolsy (tools) visible in production by configuring global OTel providers and standard GenAI attributes. Use otel.Tracer("your-module") and otel.Meter("your-module") for granular instrumentation scope.

Contributing

Contributions are welcome. Please open an issue or PR.

License

MIT. See LICENSE for details.

Documentation

Overview

Package metry provides a zero-boilerplate OpenTelemetry and LLMOps hub for Go AI applications.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrServiceNameRequired is returned when Init is called with an empty ServiceName.
	ErrServiceNameRequired = errors.New("metry: ServiceName is required")
)

Functions

func BaggageValue

func BaggageValue(ctx context.Context, key string) string

BaggageValue retrieves a value for the given key from the context's baggage. Returns an empty string if the key does not exist.

func Console

func Console() (*TraceExporter, *MetricExporter)

Console returns trace and metric exporters that write to stdout (for local development).

func Init

func Init(ctx context.Context, opts ...Option) (shutdown func(context.Context) error, err error)

Init configures global OTel providers and returns a shutdown function. Uses resource.Default() merged with service attributes for host/PID/OS and service identity. On partial failure, already-created providers are shut down and global otel state is restored to the previous tracer/meter (atomic "all or nothing" for global provider state).

func Noop

func Noop() (*TraceExporter, *MetricExporter)

Noop returns trace and metric exporters that drop all data (for disabling telemetry or tests).

func OTLPGRPC

func OTLPGRPC(endpoint string, insecure bool) (*TraceExporter, *MetricExporter)

OTLPGRPC returns trace and metric exporters that send data via OTLP over gRPC. endpoint is the target host:port (e.g. "localhost:4317"). If insecure is true, TLS is disabled.

func OTLPHTTP

func OTLPHTTP(endpoint string, headers map[string]string) (*TraceExporter, *MetricExporter)

OTLPHTTP returns trace and metric exporters that send data via OTLP over HTTP. endpoint is the target host:port (e.g. "localhost:4318"). headers are optional HTTP headers.

func SetBaggageValue added in v0.1.3

func SetBaggageValue(ctx context.Context, key, value string) (context.Context, error)

SetBaggageValue injects a key-value pair into the context's baggage. Keys and values must comply with W3C Baggage; invalid key/value returns a wrapped error.

Types

type MetricExporter

type MetricExporter struct {
	// contains filtered or unexported fields
}

MetricExporter is an opaque type that produces an OTel metric exporter. Create it via OTLPGRPC, OTLPHTTP, Console, Noop, or testutil.

func NewMetricExporterFromExporter

func NewMetricExporterFromExporter(exp metric.Exporter) *MetricExporter

NewMetricExporterFromExporter wraps an existing metric.Exporter as a MetricExporter. Used by testutil and other packages that need to plug in a custom exporter instance.

type Option added in v0.1.2

type Option func(*config)

Option configures Init. Use WithServiceName, WithTraceRatio, etc.

func WithConsole added in v0.1.2

func WithConsole() Option

WithConsole sets trace and metric exporters that write to stdout (for local dev).

func WithEnvironment added in v0.1.2

func WithEnvironment(env string) Option

WithEnvironment sets the deployment environment (e.g. "production", "staging").

func WithMaxGenAIContextLength added in v0.1.3

func WithMaxGenAIContextLength(bytes int) Option

WithMaxGenAIContextLength sets the max byte length for prompt/completion/tool strings before truncation (default 16384). Use for local dev or closed loops where full context is needed; 0 means keep default.

func WithMetricExporter added in v0.1.2

func WithMetricExporter(me MetricExporter) Option

WithMetricExporter sets the metric exporter. If not set, metrics are not exported. Accepts value to avoid mutability after Init (clean-break contract).

func WithNoop added in v0.1.2

func WithNoop() Option

WithNoop sets no-op trace and metric exporters (disable telemetry or tests).

func WithOTLPGRPC added in v0.1.2

func WithOTLPGRPC(endpoint string, insecure bool) Option

WithOTLPGRPC sets trace and metric exporters for OTLP over gRPC (e.g. "localhost:4317").

func WithOTLPHTTP added in v0.1.2

func WithOTLPHTTP(endpoint string, headers map[string]string) Option

WithOTLPHTTP sets trace and metric exporters for OTLP over HTTP (e.g. "localhost:4318").

func WithServiceName added in v0.1.2

func WithServiceName(name string) Option

WithServiceName sets the service name (required).

func WithServiceVersion added in v0.1.2

func WithServiceVersion(version string) Option

WithServiceVersion sets the service version (optional).

func WithTraceExporter added in v0.1.2

func WithTraceExporter(te *TraceExporter) Option

WithTraceExporter sets the trace exporter. If not set, a no-op exporter is used.

func WithTraceRatio added in v0.1.2

func WithTraceRatio(ratio float64) Option

WithTraceRatio sets the fraction of traces to sample (1.0 = 100%, 0.0 = disable).

type TraceExporter

type TraceExporter struct {
	// contains filtered or unexported fields
}

TraceExporter is an opaque type that produces an OTel trace exporter. Create it via OTLPGRPC, OTLPHTTP, Console, Noop, or testutil.

func NewTraceExporterFromSpanExporter

func NewTraceExporterFromSpanExporter(exp sdktrace.SpanExporter) *TraceExporter

NewTraceExporterFromSpanExporter wraps an existing sdktrace.SpanExporter as a TraceExporter. Used by testutil and other packages that need to plug in a custom exporter instance.

Directories

Path Synopsis
Package genai provides OpenLLMetry semantic convention constants and helpers for recording GenAI usage (tokens, cost, prompts) on OTel spans.
Package genai provides OpenLLMetry semantic convention constants and helpers for recording GenAI usage (tokens, cost, prompts) on OTel spans.
internal
genaimetrics
Package genaimetrics provides internal registration of GenAI metric instruments.
Package genaimetrics provides internal registration of GenAI metric instruments.
middleware
grpc
Package grpc provides gRPC instrumentation for metry: server and client stats handlers that create spans and propagate trace context.
Package grpc provides gRPC instrumentation for metry: server and client stats handlers that create spans and propagate trace context.
http
Package http provides HTTP middleware for metry that creates root spans and propagates trace context.
Package http provides HTTP middleware for metry that creates root spans and propagates trace context.
Package security provides semantic convention constants and helpers for AI security observability (tiers, validators, actions, shadow mode).
Package security provides semantic convention constants and helpers for AI security observability (tiers, validators, actions, shadow mode).
Package testutil provides in-memory exporters and helpers for testing code that uses metry.
Package testutil provides in-memory exporters and helpers for testing code that uses metry.
Package traceutil provides utilities for OpenTelemetry trace spans.
Package traceutil provides utilities for OpenTelemetry trace spans.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL