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