Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ _testmain.go
/dogstatsd

/datadog/testdata/fuzz
.claude/settings.local.json
70 changes: 70 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,76 @@
Apply 'go fix ./...' on the codebase. Several references to interface{} have
been replaced by `any`; there should be no API or performance differences.

Add full OpenTelemetry OTLP exporter support with official SDK integration.

**New Feature: OpenTelemetry OTLP Exporter (SDKHandler)**

The `otlp` package now includes a production-ready `SDKHandler` that uses the
official OpenTelemetry SDK with comprehensive support for modern observability
requirements:

- **Dual Transport Support**: Both gRPC and HTTP/Protobuf protocols
- **Environment Variables**: Support for the standard `OTEL_*` environment
variables including `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_PROTOCOL`
(and the `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL` override), and
`OTEL_RESOURCE_ATTRIBUTES`
- **Resource Detection**: Host and process metadata plus environment attributes
by default; cloud (AWS, GCP, Azure) and Kubernetes detection is opt-in via the
`contrib/detectors/*` packages, with examples in the package README
- **All Metric Types**: Counter, Gauge, and Histogram with proper semantics
- **Exponential Histograms**: Optional support for exponential histogram aggregation
with configurable bucket size and scale
- **Temporality Configuration**: Configurable metric temporality (cumulative or delta)
with cumulative as the default for Prometheus compatibility
- **Tag Preservation**: Automatic conversion of stats tags to OpenTelemetry attributes
- **Production Ready**: Thread-safe instrument caching, proper context handling,
and comprehensive error handling

**Usage Example:**

```go
import (
"context"
"github.com/segmentio/stats/v5"
"github.com/segmentio/stats/v5/otlp"
)

// Simple usage with environment variables
handler, err := otlp.NewSDKHandlerFromEnv(ctx)
if err != nil {
log.Fatal(err)
}
defer handler.Shutdown(ctx)
stats.Register(handler)

// Or with explicit configuration
handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
Protocol: otlp.ProtocolGRPC,
EndpointURL: "http://localhost:4317",
})
```

**Implementation Details:**

- Gauges use native `Float64Gauge` instrument for instantaneous value recording
- Background context for metric recording to prevent context cancellation issues
- Efficient two-level locking pattern for instrument caching (read locks in hot path)
- Cumulative temporality by default (Prometheus-compatible)
- Comprehensive documentation including cloud resource detector examples
- `SDKConfig.EndpointURL` takes a full URL including the `http://` or `https://` scheme
- SDK defaults are used when unset (ExportInterval: 60s, ExportTimeout: 30s)

**Deprecated:**

- `otlp.Handler` is now deprecated in favor of `otlp.SDKHandler` (will be removed in v6.0.0)
- `otlp.HTTPClient` is now deprecated in favor of `otlp.SDKHandler` with `ProtocolHTTPProtobuf` (will be removed in v6.0.0)
- `otlp.NewHTTPClient()` is now deprecated (will be removed in v6.0.0)

The legacy `Handler` has been marked as Alpha since 2022 and has minimal to zero usage.
Migration is straightforward - see deprecation notices in code for examples.

See the [otlp package documentation](./otlp/README.md) for complete details and examples.

### v5.8.0 (December 15, 2025)

When reporting go/stats versions, ensure that any user provided tags are
Expand Down
91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,97 @@ func main() {
}
```

## Supported Backends

The stats package supports multiple metric backends out of the box:

### OpenTelemetry (OTLP)

The [github.com/segmentio/stats/v5/otlp](https://pkg.go.dev/github.com/segmentio/stats/v5/otlp) package provides full OpenTelemetry Protocol (OTLP) support using the official OpenTelemetry SDK.

**Features:**

- gRPC and HTTP/Protobuf transports
- Full support for OTEL_* environment variables
- Automatic resource detection (cloud, Kubernetes, host, process)
- Production-ready with official OTel SDK exporters

```go
import (
"context"
"github.com/segmentio/stats/v5"
"github.com/segmentio/stats/v5/otlp"
)

func main() {
ctx := context.Background()

// Using gRPC (recommended). Note the field is EndpointURL, not Endpoint,
// and the value must include the scheme.
handler, err := otlp.NewSDKHandler(ctx, otlp.SDKConfig{
Protocol: otlp.ProtocolGRPC,
EndpointURL: "http://localhost:4317",
})
if err != nil {
panic(err)
}
defer handler.Shutdown(ctx)

stats.Register(handler)
defer stats.Flush()

// Or configure everything from environment variables (simplest). Note this
// example changes two things relative to the one above: it moves the
// configuration from in-code to env vars, AND it switches the transport
// from gRPC to HTTP/protobuf.
// export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example switches from GRPC to HTTP in addition to switching from env vars to in memory - that's fine but let's be explicit this is making two changes (env var AND protocol) instead of just one

// export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
handler, err = otlp.NewSDKHandlerFromEnv(ctx)
}
```

See the [otlp package documentation](./otlp/README.md) for complete details.

### Datadog

The [github.com/segmentio/stats/v5/datadog](https://godoc.org/github.com/segmentio/stats/v5/datadog) package provides support for sending metrics to Datadog via DogStatsD protocol over UDP or Unix Domain Sockets.

```go
import "github.com/segmentio/stats/v5/datadog"

stats.Register(datadog.NewClient("localhost:8125"))
```

### Prometheus

The [github.com/segmentio/stats/v5/prometheus](https://godoc.org/github.com/segmentio/stats/v5/prometheus) package exposes an HTTP handler that serves metrics in Prometheus format.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is dumb but for people who have never used Prometheus before, the pull model vs. push can be counterintuitive, could we just add a sentence explaining that's how this works? "Note that with Prometheus, the metric server will poll your client for changes - metrics are not pushed from a client to the server"


Note that with Prometheus the metric server polls your client for changes —
metrics are pulled by the server, not pushed from the client. This is the
opposite of most other backends here (Datadog, InfluxDB, OTLP), where the
client pushes metrics to the server.

```go
import (
"net/http"
"github.com/segmentio/stats/v5/prometheus"
)

handler := prometheus.NewHandler()
stats.Register(handler)
http.Handle("/metrics", handler)
```

### InfluxDB

The [github.com/segmentio/stats/v5/influxdb](https://godoc.org/github.com/segmentio/stats/v5/influxdb) package sends metrics to InfluxDB using the line protocol over HTTP.

```go
import "github.com/segmentio/stats/v5/influxdb"

stats.Register(influxdb.NewClient("http://localhost:8086"))
```

### Metrics

- [Gauges](https://godoc.org/github.com/segmentio/stats#Gauge)
Expand Down
6 changes: 4 additions & 2 deletions measure.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,10 +412,12 @@ func (tags tagFuncMap) copy() tagFuncMap {
}

func (tags tagFuncMap) namedTagFuncs() []namedTagFunc {
namedTags := make([]namedTagFunc, 0, len(tags))
namedTags := make([]namedTagFunc, len(tags))

i := 0
for name, fn := range tags {
namedTags = append(namedTags, namedTagFunc{name: name, fn: fn})
namedTags[i] = namedTagFunc{name: name, fn: fn}
i++
}

return namedTags
Expand Down
Loading
Loading