OpenTelemetry Gorilla Mux monitoring [otelmux]
OpenTelemetry Gorilla Mux instrumentation allows developers to monitor and diagnose issues with their Gorilla Mux applications, providing valuable insights into HTTP request handling and routing performance.
What is Gorilla Mux?
Gorilla Mux is one of the oldest and most popular HTTP routers for Go. It implements a request router and dispatcher that matches incoming requests to their respective handlers based on URL patterns.
Key features of Gorilla Mux include:
- Powerful URL pattern matching with variables and regex support
- Route grouping with subrouters
- Middleware support through handlers chain
- URL building and reverse routing
- Request matching based on headers, query parameters, and more
What is OpenTelemetry?
OpenTelemetry is an open-source observability framework that aims to standardize and simplify the collection, processing, and export of telemetry data from applications and systems.
OpenTelemetry supports multiple programming languages and platforms, making it suitable for a wide range of applications and environments.
OpenTelemetry enables developers to instrument their code and collect telemetry data, which can then be exported to various OpenTelemetry backends or observability platforms for analysis and visualization. Understanding the OpenTelemetry architecture helps developers leverage its full potential with proper instrumentation and data collection strategies.
Gorilla Mux instrumentation
OpenTelemetry Gorilla Mux instrumentation (otelmux) provides automatic tracing for your Gorilla Mux applications. It captures HTTP request details, route parameters, timing information, and error states.
To install otelmux instrumentation:
go get go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux
Usage
You can instrument Gorilla Mux router by installing OpenTelemetry middleware:
import (
"net/http"
"github.com/gorilla/mux"
"go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux"
)
func main() {
router := mux.NewRouter()
router.Use(otelmux.Middleware("my-service"))
router.HandleFunc("/users/{id}", getUser).Methods("GET")
router.HandleFunc("/articles/{category}/{id:[0-9]+}", getArticle).Methods("GET")
http.ListenAndServe(":8080", router)
}
Once instrumented, otelmux automatically creates spans for each incoming HTTP request, capturing the HTTP method, route pattern, status code, and timing information.
Middleware options
The otelmux middleware supports several configuration options:
| Option | Description |
|---|---|
WithTracerProvider | Use a custom TracerProvider instead of the global one |
WithMeterProvider | Use a custom MeterProvider instead of the global one |
WithPropagators | Specify propagators for extracting trace context |
WithSpanOptions | Configure additional span start options |
WithSpanNameFormatter | Customize span names for requests |
WithPublicEndpoint | Mark the endpoint as public (starts new trace) |
WithPublicEndpointFn | Conditionally mark endpoints as public |
Custom tracer provider
For more control over telemetry collection, you can specify a custom tracer provider:
import (
"github.com/gorilla/mux"
"go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux"
"go.opentelemetry.io/otel"
)
router := mux.NewRouter()
router.Use(otelmux.Middleware("my-service",
otelmux.WithTracerProvider(otel.GetTracerProvider()),
otelmux.WithMeterProvider(otel.GetMeterProvider()),
))
Custom span names
Customize how span names are generated:
import (
"net/http"
"github.com/gorilla/mux"
"go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux"
)
router := mux.NewRouter()
router.Use(otelmux.Middleware("my-service",
otelmux.WithSpanNameFormatter(func(routeName string, r *http.Request) string {
return r.Method + " " + routeName
}),
))
Public endpoints
Mark endpoints as public to start new traces instead of continuing from incoming trace context:
import (
"net/http"
"github.com/gorilla/mux"
"go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux"
)
// Mark all endpoints as public
router.Use(otelmux.Middleware("my-service",
otelmux.WithPublicEndpoint(),
))
// Or conditionally mark endpoints as public
router.Use(otelmux.Middleware("my-service",
otelmux.WithPublicEndpointFn(func(r *http.Request) bool {
return r.URL.Path == "/webhook"
}),
))
Adding custom attributes
You can add custom attributes to spans within your handlers by accessing the span from the request context:
import (
"net/http"
"github.com/gorilla/mux"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
func getUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
userID := vars["id"]
// Get the current span from context
span := trace.SpanFromContext(r.Context())
// Add custom attributes
span.SetAttributes(
attribute.String("user.id", userID),
attribute.String("user.request_type", "profile"),
)
// Your handler logic here
w.WriteHeader(http.StatusOK)
}
Recording errors
Record errors in your handlers to mark spans as failed:
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)
func getUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
userID := vars["id"]
span := trace.SpanFromContext(r.Context())
user, err := fetchUser(r.Context(), userID)
if err != nil {
// Record the error on the span
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
http.Error(w, "User not found", http.StatusNotFound)
return
}
// Continue with successful response
json.NewEncoder(w).Encode(user)
}
Filtering routes
Exclude health checks and other noisy endpoints from tracing by wrapping the middleware:
import (
"net/http"
"strings"
"github.com/gorilla/mux"
"go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux"
)
// Middleware wrapper that skips tracing for certain paths
func tracingMiddleware(excludePaths []string) mux.MiddlewareFunc {
otelMiddleware := otelmux.Middleware("my-service")
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Skip tracing for excluded paths
for _, path := range excludePaths {
if strings.HasPrefix(r.URL.Path, path) {
next.ServeHTTP(w, r)
return
}
}
// Apply OTel middleware for other routes
otelMiddleware(next).ServeHTTP(w, r)
})
}
}
func main() {
router := mux.NewRouter()
// Skip tracing for health and metrics endpoints
router.Use(tracingMiddleware([]string{"/health", "/ready", "/metrics"}))
router.HandleFunc("/health", healthCheck).Methods("GET")
router.HandleFunc("/api/users", listUsers).Methods("GET")
}
Subrouters and route grouping
Instrument subrouters for organized route grouping:
import (
"github.com/gorilla/mux"
"go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux"
)
func main() {
router := mux.NewRouter()
router.Use(otelmux.Middleware("my-service"))
// API v1 subrouter
apiV1 := router.PathPrefix("/api/v1").Subrouter()
apiV1.HandleFunc("/users", listUsers).Methods("GET")
apiV1.HandleFunc("/users/{id}", getUser).Methods("GET")
// Admin subrouter
admin := router.PathPrefix("/admin").Subrouter()
admin.HandleFunc("/dashboard", adminDashboard).Methods("GET")
}
Outgoing HTTP requests
To trace outgoing HTTP requests from your handlers, use the otelhttp transport:
import (
"net/http"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
// Create an instrumented HTTP client
var httpClient = &http.Client{
Transport: otelhttp.NewTransport(http.DefaultTransport),
}
func getUser(w http.ResponseWriter, r *http.Request) {
// Outgoing request will be traced and linked to the parent span
req, _ := http.NewRequestWithContext(r.Context(), "GET", "https://api.example.com/user/123", nil)
resp, err := httpClient.Do(req)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer resp.Body.Close()
// Process response...
}
The otelhttp transport automatically:
- Creates child spans for each outgoing request
- Injects trace context headers (W3C Trace Context)
- Records HTTP method, URL, status code, and duration
- Propagates errors to the span
HTTP metrics
The otelmux middleware automatically collects HTTP server metrics following OpenTelemetry semantic conventions:
| Metric | Description |
|---|---|
http.server.request.duration | Duration of HTTP server requests |
http.server.request.body.size | Size of HTTP server request bodies |
http.server.response.body.size | Size of HTTP server response bodies |
What is Uptrace?
Uptrace is an OpenTelemetry APM that supports distributed tracing, metrics, and logs. You can use it to monitor applications and troubleshoot issues.

Uptrace comes with an intuitive query builder, rich dashboards, alerting rules with notifications, and integrations for most languages and frameworks.
Uptrace can process billions of spans and metrics on a single server and allows you to monitor your applications at 10x lower cost.
In just a few minutes, you can try Uptrace by visiting the cloud demo (no login required) or running it locally with Docker. The source code is available on GitHub.
FAQ
What is otelmux? The otelmux package is the official OpenTelemetry instrumentation for Gorilla Mux. It provides middleware that automatically creates spans for incoming HTTP requests, capturing method, route, status code, and timing information.
How do I filter health check endpoints? Use the WithPublicEndpointFn option to conditionally handle specific endpoints, or implement custom logic in your handlers. Unlike some frameworks, otelmux doesn't have a built-in filter option, but you can wrap the middleware with your own filtering logic.
What metrics does otelmux collect? otelmux automatically collects http.server.request.duration, http.server.request.body.size, and http.server.response.body.size metrics following OpenTelemetry semantic conventions.
Does otelmux capture route variables? Yes, the route template (e.g., /users/{id}) is captured in the span name and attributes. The actual variable values can be added as custom attributes using mux.Vars(r) in your handlers.
How do I propagate trace context to downstream services? otelmux automatically extracts incoming trace context from headers. For outgoing requests, use otelhttp.NewTransport to inject trace context into your HTTP client calls.
Can I use otelmux with Gorilla Mux subrouters? Yes, add the middleware to the root router and it will trace requests across all subrouters. The span will include the full matched route path.
What's the difference between WithPublicEndpoint and WithPublicEndpointFn? WithPublicEndpoint marks all endpoints as public (starting new traces), while WithPublicEndpointFn lets you conditionally mark endpoints based on the request, useful for webhooks or external API callbacks.
What's next?
With OpenTelemetry Gorilla Mux instrumentation in place, you can monitor request latency, track error rates, and trace requests across your distributed systems.
Next steps to enhance your observability:
- Add database instrumentation with GORM or database/sql
- Correlate logs with traces using Logrus, Zap, or slog
- Create custom spans using the OpenTelemetry Go Tracing API
- Explore other Go frameworks like Gin or Echo
- Set up the OpenTelemetry Collector for production deployments