otel

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package otel turns the drops.Hook / drops.QueryEvent stream into OpenTelemetry spans and metrics — without importing OpenTelemetry.

Every drops DB and cache backend accepts a drops.Hook that fires after each operation. This package builds a Hook that, for each event, opens a span sized to the operation and records the RED metrics (rate, errors, duration). It talks to small OTel-shaped interfaces (Tracer, Span, Meter, Int64Counter, Float64Histogram) rather than the real SDK, so drops carries no external dependency — exactly as drops/pg's Tracer does. Bridging to the real go.opentelemetry.io/otel SDK is a handful of adapter lines (see the package example).

Because it is driven purely by the Hook, one Instrumentation works for every dialect (pg, sqlite, clickhouse) and every cache backend (memory, redis, memcached, tiered):

inst := otel.New(otel.Config{Tracer: tr, Meter: mt, System: "postgresql"})
db := pg.New(drv).WithHook(inst.Hook())

The hook fires after the operation completes, so spans are created retroactively with explicit start/end timestamps (start = observed end − event Duration). For live, actively-wrapped spans on PostgreSQL, combine this with pg.WithTracer; the two compose.

Example

Example shows the wiring shape. In real code the Tracer and Meter are thin adapters over the go.opentelemetry.io/otel SDK, for example:

type tracerAdapter struct{ t trace.Tracer }

func (a tracerAdapter) Start(ctx context.Context, name string, start time.Time) (context.Context, otel.Span) {
    ctx, s := a.t.Start(ctx, name, trace.WithTimestamp(start))
    return ctx, spanAdapter{s}
}

type spanAdapter struct{ s trace.Span }

func (a spanAdapter) SetAttributes(attrs ...otel.Attr) {
    kv := make([]attribute.KeyValue, len(attrs))
    for i, at := range attrs { kv[i] = toKV(at) }
    a.s.SetAttributes(kv...)
}
func (a spanAdapter) RecordError(err error) { a.s.RecordError(err) }
func (a spanAdapter) End(end time.Time)     { a.s.End(trace.WithTimestamp(end)) }

then:

inst := otel.New(otel.Config{Tracer: tracerAdapter{tr}, Meter: meterAdapter{mt}, System: "postgresql"})
db := pg.New(drv).WithHook(inst.Hook())

Here we use in-memory fakes so the example is self-contained.

m := newMeter()
inst := otel.New(otel.Config{Meter: m, System: "postgresql"})
hook := inst.Hook()

// Simulate what a drops DB emits after two operations.
hook(context.Background(), drops.QueryEvent{Kind: "query", Duration: 2 * time.Millisecond})
hook(context.Background(), drops.QueryEvent{Kind: "exec", Duration: 1 * time.Millisecond})

fmt.Println("operations recorded:", len(m.counters[otel.MetricCalls].adds))
fmt.Println("latency samples:", len(m.histograms[otel.MetricDuration].recs))
Output:
operations recorded: 2
latency samples: 2

Index

Examples

Constants

View Source
const (
	AttrSystem    = "db.system"
	AttrOperation = "db.operation"
	AttrStatement = "db.statement"
	AttrArgsCount = "db.args.count"
	AttrError     = "error"
)

Semantic-convention attribute keys used on drops spans and metrics. They follow the OpenTelemetry database conventions so downstream dashboards and alerts can match on stable keys.

View Source
const (
	MetricDuration = "db.client.operation.duration"
	MetricCalls    = "db.client.operations"
	MetricErrors   = "db.client.errors"
)

Instrument names. Duration is recorded in seconds, per the OTel database-client conventions.

Variables

This section is empty.

Functions

This section is empty.

Types

type Attr

type Attr struct {
	Key   string
	Value any
}

Attr is a single span/metric attribute. Value is typically a string, int64, float64 or bool; the adapter maps it to the real OTel attribute.KeyValue.

type Config

type Config struct {
	// Tracer, if set, receives one span per operation. Nil disables
	// span emission.
	Tracer Tracer

	// Meter, if set, receives the RED metrics. Nil disables metrics.
	Meter Meter

	// System is the db.system attribute value, e.g. "postgresql",
	// "sqlite", "clickhouse", or "redis" for a cache. Optional.
	System string

	// RecordStatement attaches the rendered SQL text as the db.statement
	// span attribute. Off by default because statements can embed
	// sensitive literals in non-parameterised fragments; enable only when
	// the statement text is safe to export.
	RecordStatement bool

	// SpanName overrides the span name derived from an event. The default
	// is "<System> <Kind>" (e.g. "postgresql query"), or just the Kind
	// when System is empty.
	SpanName func(e drops.QueryEvent) string

	// Now is injectable for tests; defaults to time.Now. It is used to
	// timestamp the (retroactive) span end.
	Now func() time.Time
}

Config configures an Instrumentation. Tracer and Meter are each optional: supply a Tracer for spans, a Meter for metrics, or both.

type Float64Histogram

type Float64Histogram interface {
	Record(ctx context.Context, value float64, attrs ...Attr)
}

Float64Histogram mirrors metric.Float64Histogram.

type Instrumentation

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

Instrumentation holds the pre-created metric instruments and the configuration. Build it once and share the Hook it produces.

func New

func New(cfg Config) *Instrumentation

New builds an Instrumentation. Metric instruments are created eagerly from the Meter (if any) so the hot path only records.

func (*Instrumentation) Hook

func (i *Instrumentation) Hook() drops.Hook

Hook returns a drops.Hook that emits spans and metrics for every operation. When neither a Tracer nor a Meter is configured it returns nil, which drops treats as "no hook" — zero overhead.

type Int64Counter

type Int64Counter interface {
	Add(ctx context.Context, incr int64, attrs ...Attr)
}

Int64Counter mirrors metric.Int64Counter.

type Meter

type Meter interface {
	Int64Counter(name string) Int64Counter
	Float64Histogram(name string) Float64Histogram
}

Meter mirrors the subset of go.opentelemetry.io/otel/metric.Meter that drops needs. The two instrument constructors are called once at New.

type Span

type Span interface {
	SetAttributes(attrs ...Attr)
	RecordError(err error)
	End(endTime time.Time)
}

Span is the per-operation handle returned by Tracer.Start. End closes the span at endTime (trace.WithTimestamp(endTime) in the adapter).

type Tracer

type Tracer interface {
	Start(ctx context.Context, name string, startTime time.Time) (context.Context, Span)
}

Tracer mirrors the subset of go.opentelemetry.io/otel/trace.Tracer that drops needs. Start opens a span named name that began at startTime; pass the equivalent of trace.WithTimestamp(startTime) in the adapter.

Jump to

Keyboard shortcuts

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