Skip to content

Metrics

The observe.Metrics interface is a factory for named metric instruments that decouple measurement from the collection backend.

Interface

type Metrics interface {
    Counter(name string, tags map[string]string) Counter
    Histogram(name string, tags map[string]string) Histogram
    Gauge(name string, tags map[string]string) Gauge
}

Each factory method returns a typed instrument. Tags are label key-value pairs that the backend attaches to every observation.

Instruments

Counter

A monotonically increasing value. Use for request counts, error counts, retry counts.

type Counter interface {
    Inc()
    Add(delta float64)
}

Histogram

Records observations in configurable buckets. Use for latencies, payload sizes, queue depths.

type Histogram interface {
    Observe(value float64)
}

Gauge

A value that can go up or down. Use for active tasks, queue length, memory usage.

type Gauge interface {
    Set(value float64)
    Inc()
    Dec()
}

Standard Metrics

DagNats components emit these metrics automatically:

MetricTypeComponentDescription
step.duration_msHistogramWorkerTime to execute a task handler
step.retriesCounterWorkerNumber of handler errors that triggered retry
worker.tasks.activeGaugeWorkerNumber of tasks currently being processed
bridge.requestsCounterBridgeTotal HTTP bridge requests
bridge.request.duration_msHistogramBridgeHTTP bridge request latency
bridge.ackmap.sizeGaugeBridgeNumber of unresolved tasks in the ack map
telemetry.spans.droppedCounterTraceCollectorSpans dropped due to full buffer

Usage

Instruments are created once at construction time and reused:

duration := tel.Metrics.Histogram("step.duration_ms", nil)
retries := tel.Metrics.Counter("step.retries", nil)
active := tel.Metrics.Gauge("worker.tasks.active", nil)

// In the hot path
active.Inc()
start := time.Now()
err := handler(ctx)
duration.Observe(float64(time.Since(start).Milliseconds()))
active.Dec()
if err != nil {
    retries.Inc()
}

Tags allow partitioning metrics by dimension:

counter := tel.Metrics.Counter("requests", map[string]string{
    "task_type": "send-email",
    "status":    "success",
})
counter.Inc()

Adapter Pattern

The noop implementation discards all observations. Use it for tests and environments where metrics are not needed:

metrics := observe.NewNoopMetrics()

The internal simple.MetricsCollector publishes MetricPoint records as JSON to the NATS TELEMETRY stream at telemetry.metrics.{service}.{name}. For production, implement the Metrics interface with your preferred backend (Prometheus, OTel, StatsD):

type PrometheusMetrics struct { /* ... */ }

func (p *PrometheusMetrics) Counter(
    name string, tags map[string]string,
) observe.Counter {
    // Return a Prometheus counter vec with the given labels
}

Related