A lightweight, flexible metrics library for Go that provides a unified interface for collecting application metrics with support for multiple backends including Prometheus and expvar.
- Multiple metric types: Counters, Gauges, Histograms, and Timers
- Label/tag support: Multi-dimensional metrics with labels
- Scoped metrics: Hierarchical metric organization with namespace and tag inheritance
- Multiple backends: Prometheus, expvar, or custom collectors
- Thread-safe: Concurrent metric updates without locks
- Zero allocations: Optimized for high-performance applications
- Flexible histograms: Configurable bucket boundaries
go get github.com/upfluence/statspackage main
import (
"github.com/upfluence/stats""github.com/upfluence/stats/prometheus"
)
funcmain() {
// Create a Prometheus collectorcollector:=prometheus.NewCollector()
// Create a root scopescope:=stats.RootScope(collector)
// Create and use metricscounter:=scope.Counter("requests_total")
counter.Inc()
gauge:=scope.Gauge("active_connections")
gauge.Update(42)
histogram:=scope.Histogram("request_duration_seconds")
histogram.Record(0.123)
}Counters are monotonically increasing values, ideal for tracking totals.
// Simple countercounter:=scope.Counter("http_requests_total")
counter.Inc()
counter.Add(5)
// Counter with labelscounterVec:=scope.CounterVector("http_requests_total", []string{"method", "status"})
counterVec.WithLabels("GET", "200").Inc()
counterVec.WithLabels("POST", "201").Add(10)Gauges represent values that can go up or down.
// Simple gaugegauge:=scope.Gauge("memory_usage_bytes")
gauge.Update(1024000)
// Gauge with labelsgaugeVec:=scope.GaugeVector("cpu_usage_percent", []string{"core"})
gaugeVec.WithLabels("0").Update(75)
gaugeVec.WithLabels("1").Update(82)Histograms track distributions of values across buckets.
// Default histogram (uses standard buckets)histogram:=scope.Histogram("response_time_seconds")
histogram.Record(0.234)
// Custom bucketshistogram:=scope.Histogram("response_time_seconds",
stats.StaticBuckets([]float64{0.01, 0.05, 0.1, 0.5, 1.0}))
histogram.Record(0.123)
// Histogram with labelshistVec:=scope.HistogramVector("request_duration_seconds",
[]string{"endpoint", "method"})
histVec.WithLabels("/api/users", "GET").Record(0.045)Timers are convenience wrappers around histograms for measuring durations.
timer:=stats.NewTimer(scope, "operation_duration")
stopwatch:=timer.Start()
// ... perform operation ...stopwatch.Stop() // Records duration in seconds// Timer with custom suffix and bucketstimer:=stats.NewTimer(scope, "db_query",
stats.WithTimerSuffix("_ms"),
stats.WithHistogramOptions(
stats.StaticBuckets([]float64{1, 5, 10, 50, 100})))Instruments provide automatic instrumentation for function execution, combining multiple metrics into a single convenient interface. They automatically track:
- Started counter: Number of times the operation started (optional)
- Finished counter: Number of completions, labeled by status (success/failed)
- Duration histogram: Execution time distribution
// Basic instrumentinstrument:=stats.NewInstrument(scope, "api_request")
err:=instrument.Exec(func() error {
// Your operation herereturnprocessRequest()
})
// Automatically records:// - api_request_started_total (counter)// - api_request_total{status="success"} or {status="failed"} (counter)// - api_request_duration_seconds (histogram)// Instrument with labelsinstrumentVec:=stats.NewInstrumentVector(
scope,
"database_query",
[]string{"table", "operation"},
)
err:=instrumentVec.WithLabels("users", "select").Exec(func() error {
returndb.Query("SELECT * FROM users")
})
// Custom optionsinstrument:=stats.NewInstrument(scope, "task",
// Disable the started counterstats.DisableStartedCounter(),
// Custom status label name (default: "status")stats.WithCounterLabel("result"),
// Custom error formatterstats.WithFormatter(func(errerror) string {
iferr==nil {
return"ok"
}
return"error"
}),
// Custom timer optionsstats.WithTimerOptions(
stats.WithTimerSuffix("_duration_ms"),
stats.WithHistogramOptions(
stats.StaticBuckets([]float64{10, 50, 100, 500, 1000}),
),
),
)Error Formatters: Customize how errors are categorized in the status label:
// Advanced error formatter using error typesformatter:=func(errerror) string {
iferr==nil {
return"success"
}
switch {
caseerrors.Is(err, context.DeadlineExceeded):
return"timeout"caseerrors.Is(err, context.Canceled):
return"canceled"caseisValidationError(err):
return"validation_error"default:
return"internal_error"
}
}
instrument:=stats.NewInstrument(
scope,
"operation",
stats.WithFormatter(formatter),
)Generated Metrics:
For an instrument named "api_request", the following metrics are created:
api_request_started_total- Counter tracking how many times the operation started (useful for computing in-flight count:started_total - sum(total))api_request_total{status="..."}- Counter tracking completions by statusapi_request_duration_seconds- Histogram of execution durations
Scopes allow you to organize metrics hierarchically with namespace prefixes and tag inheritance.
// Create a child scope with namespace and tagsdbScope:=scope.Scope("database", map[string]string{
"service": "user-api",
"env": "production",
})
// Metrics created from this scope inherit the namespace and tagscounter:=dbScope.Counter("queries")
// Results in metric name: database_queries// with tags: service=user-api, env=production// Nested scopesredisScope:=dbScope.Scope("redis", map[string]string{
"cluster": "cache-01",
})
// Metrics will have namespace: database_redis// and tags: service=user-api, env=production, cluster=cache-01Export metrics to Prometheus:
import (
"github.com/upfluence/stats/prometheus""github.com/prometheus/client_golang/prometheus/promhttp""net/http"
)
collector:=prometheus.NewCollector()
scope:=stats.RootScope(collector)
// Expose metrics endpointhttp.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8080", nil)Push metrics to a Prometheus Push Gateway:
import (
"github.com/upfluence/stats/prometheus""github.com/upfluence/log""time"
)
exporter:=prometheus.NewExporter(
log.Default,
"http://pushgateway:9091",
"my-application",
prometheus.WithInterval(10*time.Second),
prometheus.WithGrouping("instance", "server-01"),
)
deferexporter.Close()Export metrics via Go's expvar package:
import"github.com/upfluence/stats/expvar"collector:=expvar.NewCollector()
scope:=stats.RootScope(collector)
// Metrics are automatically available at /debug/varsUse multiple collectors simultaneously:
import"github.com/upfluence/stats/multi"promCollector:=prometheus.NewCollector()
expvarCollector:=expvar.NewCollector()
collector:=multi.WrapCollectors(promCollector, expvarCollector)
scope:=stats.RootScope(collector)
// Metrics are sent to both collectorsFor testing or conditional metric collection:
varscope stats.ScopeifmetricsEnabled {
scope=stats.RootScope(collector)
} else {
scope=stats.NoopScope
}
// Code works the same, but NoopScope doesn't collect anythingscope.Counter("requests").Inc()Track metrics across service restarts while maintaining historical data:
scope:=stats.NewMultiIncarnationScope(
collector,
"app_version",
"1.2.3",
)
// Metrics include the incarnation label automaticallycounter:=scope.Counter("requests")- Reuse metric instances: Create metrics once and reuse them rather than creating new ones for each operation
- Use meaningful names: Follow naming conventions like
<namespace>_<metric>_<unit> - Limit cardinality: Be careful with label values to avoid metric explosion
- Choose appropriate types: Use counters for totals, gauges for current values, histograms for distributions
- Use scopes: Organize related metrics under common scopes
- Use instruments for functions: When tracking function execution, prefer
Instrumentover manually managing counters and timers
This library is optimized for high-performance scenarios:
- Lock-free atomic operations for counters and gauges
- Efficient label marshaling and hashing
- Minimal memory allocations
- Object pooling for timers
See LICENSE file for details.