metry

package module
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Mar 14, 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 / GlobalTracer / GlobalMeter]
    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)

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"
	"github.com/skosovsky/metry/genai"
)

// Inside your LLM call handler:
ctx, span := metry.GlobalTracer().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...")

Spans tagged with gen_ai.usage.* and gen_ai.prompt / gen_ai.completion are recognized by OpenLLMetry-compatible backends. Long prompt/completion strings are truncated to 16 KB to protect OTLP export pipelines. The library follows a clean-break policy: initialization is via metry.Init only (no genai.Init or legacy options). The test suite validates the lifecycle Init -> shutdown -> Init by asserting that TTFT and usage datapoints are actually exported after the second Init.

Agentic & RAG Tracing

Tag tool calls and cache hits on spans so backends can show tool usage and RAG behavior. Use events for agent steps so ReAct loops (Thought -> Action -> Observation) appear as a chronological list in Jaeger/Tempo:

// Before executing a tool (e.g. inside toolsy):
genai.RecordToolCall(span, "search", "call-1", `{"q":"weather"}`)

// After the tool returns (e.g. result or error):
genai.RecordToolResult(span, "search", `{"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):
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 must be valid W3C baggage identifiers (no spaces or special characters like /).

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

// 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 providing a single tracer and meter and standard GenAI attributes.

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 ContextWithBaggage

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

ContextWithBaggage injects a key-value pair into the context's baggage. This data will automatically propagate across HTTP and gRPC boundaries.

func GlobalMeter

func GlobalMeter() metric.Meter

GlobalMeter returns the global Meter for the library (name "metry"). Call after Init.

func GlobalTracer

func GlobalTracer() trace.Tracer

GlobalTracer returns the global Tracer for the library (name "metry"). Call after Init.

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.

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 WithMetricExporter added in v0.1.2

func WithMetricExporter(me *MetricExporter) Option

WithMetricExporter sets the metric exporter. If not set, metrics are not exported.

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.
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.

Jump to

Keyboard shortcuts

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