otelroute

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Apr 7, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

README

go-otelroute

CI codecov Go Reference

Go HTTP middleware that enriches active OpenTelemetry spans with missing semantic convention attributes.

The problem

Standard net/http instrumentation (e.g. otelhttp) cannot populate http.route unless your router explicitly sets it. Without it, every unique URL becomes a separate metric series:

# Without http.route — unbounded cardinality
http_server_request_duration{http_target="/users/1"}
http_server_request_duration{http_target="/users/2"}
http_server_request_duration{http_target="/users/3"}
...

# With http.route — one series per route
http_server_request_duration{http_route="/users/*"}

What it sets

Attribute Source
http.route Inferred from URL path via clusterurl, or set explicitly via SetRoute
client.address X-Forwarded-ForX-Real-IPRemoteAddr
network.protocol.version r.Proto (e.g. "1.1", "2.0")

Attributes are set only when there is a recording span in the request context. It is a no-op otherwise.

Installation

go get github.com/t00mas/go-otelroute

Usage

With net/http and otelhttp

Wrap your handler with otelroute.New inside otelhttp.NewHandler:

import (
    "net/http"

    otelroute "github.com/t00mas/go-otelroute"
    "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

mux := http.NewServeMux()
mux.HandleFunc("/users/", handleUsers)

enriched, err := otelroute.New(mux)
if err != nil {
    log.Fatal(err)
}

// otelhttp creates the span; otelroute enriches it
http.ListenAndServe(":8080", otelhttp.NewHandler(enriched, "server"))
With a router that knows its routes (e.g. chi, gorilla/mux)

If your router can supply the matched route pattern, call SetRoute from your handler or middleware so the exact pattern is used instead of the inferred one:

import otelroute "github.com/t00mas/go-otelroute"

func myMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // e.g. chi.RouteContext(r.Context()).RoutePattern()
        otelroute.SetRoute(r, routePattern)
        next.ServeHTTP(w, r)
    })
}
Custom clusterer
import (
    "github.com/grafana/clusterurl/pkg/clusterurl"
    otelroute "github.com/t00mas/go-otelroute"
)

cfg := clusterurl.DefaultConfig()
cfg.CacheSize = 4096

c, err := clusterurl.NewClusterURLClassifier(cfg)
if err != nil {
    log.Fatal(err)
}

mw, err := otelroute.New(myHandler, otelroute.WithClusterer(c))

License

Apache 2.0 — same as OpenTelemetry and clusterurl.

Documentation

Overview

Package otelroute provides an HTTP middleware that enriches active OpenTelemetry spans with missing HTTP semantic convention attributes.

Standard net/http instrumentation (e.g. otelhttp) cannot populate http.route without router cooperation, leading to unbounded metric cardinality. This middleware infers the route from the URL path using the clusterurl ML model and also adds client.address and network.protocol.version.

Usage:

h, err := otelroute.New(myHandler)
if err != nil {
    log.Fatal(err)
}
http.ListenAndServe(":8080", otelhttp.NewHandler(h, "server"))

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(next http.Handler, opts ...Option) http.Handler

New wraps next and returns an http.Handler that enriches the active OTel span with missing HTTP semantic convention attributes after the wrapped handler returns:

  • http.route — inferred via clusterurl or set explicitly via SetRoute
  • client.address — real client IP, proxy-header aware
  • network.protocol.version — e.g. "1.1" or "2.0"

It is a no-op when there is no recording span in the request context.

func SetRoute

func SetRoute(r *http.Request, route string)

SetRoute explicitly marks the matched route pattern on the request so the middleware uses it instead of inferring it from the URL path. Call this from your router or handler when you know the template (e.g. "/users/{id}").

The request must have passed through the otelroute middleware for this to have any effect.

Types

type Option

type Option func(*config)

Option configures the middleware.

func WithClusterer

func WithClusterer(c *clusterurl.ClusterURLClassifier) Option

WithClusterer sets a pre-built ClusterURLClassifier for route inference. Use this when you want full control over the clusterer configuration.

Jump to

Keyboard shortcuts

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