OpenTelemetry Echo by Labstack [echo-otel]
OpenTelemetry Echo instrumentation allows developers to monitor and diagnose issues with their Echo applications, providing valuable insights into application behavior in production.
Quick Setup
| Step | Action | Code/Command |
|---|---|---|
| 1. Install | Install echo-otel package | go get github.com/labstack/echo-otel/v4 |
| 2. Import | Import echootel | import echootel "github.com/labstack/echo-otel/v4" |
| 3. Instrument | Add middleware to Echo | e.Use(echootel.NewMiddleware("api.example.com")) |
| 4. Verify | Check your backend for traces | Traces and metrics collected automatically |
Minimal working example:
import (
echootel "github.com/labstack/echo-otel/v4"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
e.Use(echootel.NewMiddleware("api.example.com"))
e.GET("/", func(c echo.Context) error {
return c.JSON(200, map[string]string{"message": "Hello World"})
})
e.Start(":8080")
}
This single middleware call automatically captures all incoming HTTP requests, traces request flow, records HTTP metrics, and exports telemetry data to your configured backend.
What is Echo?
Echo is a high performance, extensible, minimalist web framework for Go. It features a highly optimized HTTP router with zero dynamic memory allocations, making it one of the fastest Go web frameworks available.
Echo's key features include:
- Optimized router with zero dynamic memory allocation
- Middleware support with built-in middleware
- Data binding for JSON, XML, and form payloads
- HTTP/2 support
- Automatic TLS via Let's Encrypt
Echo's simplicity and performance make it a popular choice for building web applications and RESTful APIs in Go.
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. The OpenTelemetry architecture provides a modular, vendor-neutral approach to observability.
Configuration can be managed through OpenTelemetry environment variables, providing a standardized way to configure exporters, resource attributes, and sampling behavior across environments.
Echo instrumentation
The OpenTelemetry middleware for Echo is maintained by the Echo team in the echo-otel repository (Go package echootel). It provides automatic tracing and metrics collection for your Echo applications. It captures HTTP request details, timing information, and error states without requiring manual instrumentation.
The module's major version follows the Echo major version:
| Echo version | Module | Minimal Echo version |
|---|---|---|
| Echo v4 | github.com/labstack/echo-otel/v4 | v4.15.4 |
| Echo v5 | github.com/labstack/echo-otel/v5 | v5.2.1 |
To install echo-otel instrumentation for Echo v4:
go get github.com/labstack/echo-otel/v4
Always include the major version suffix (/v4 or /v5); go get github.com/labstack/echo-otel without it fails.
Usage
You can instrument Echo router by installing OpenTelemetry middleware:
import (
echootel "github.com/labstack/echo-otel/v4"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
e.Use(middleware.Recover())
e.Use(echootel.NewMiddleware("api.example.com"))
e.GET("/users/:id", getUser)
e.Start(":8080")
}
Once instrumented, echo-otel automatically creates spans for each incoming HTTP request, capturing method, route, status code, and timing information.
The argument of NewMiddleware is the server name, not the service name. It is recorded as the server.address and server.port span attributes, so it must be a host name with an optional port, for example api.example.com or api.example.com:8080. NewMiddleware panics on a value without a host such as :8080; pass an empty string to use the request Host header instead. Set the service name with the OTEL_SERVICE_NAME environment variable or a resource when you configure the OpenTelemetry SDK.
The middleware records a panic and then re-panics, so add middleware.Recover() before it, as shown above.
Echo v5
For Echo v5, use the /v5 module. The API is the same, but handlers and callbacks take *echo.Context:
import (
echootel "github.com/labstack/echo-otel/v5"
"github.com/labstack/echo/v5"
)
func main() {
e := echo.New()
e.Use(echootel.NewMiddleware("api.example.com"))
e.GET("/", func(c *echo.Context) error {
return c.JSON(200, map[string]string{"message": "Hello World"})
})
e.Start(":8080")
}
Migrating from otelecho
go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho is deprecated in favor of echo-otel, and so is the interim github.com/labstack/echo-opentelemetry module. You don't need to upgrade to Echo v5 to migrate: use echo-otel/v4 with Echo v4.
- Replace the import with
echootel "github.com/labstack/echo-otel/v4". - Replace
otelecho.Middleware("my-server")withechootel.NewMiddleware("my-server"), andotelecho.Middleware("my-server", opts...)withechootel.NewMiddlewareWithConfig(echootel.Config{ServerName: "my-server", ...}). Make sure the name is a valid host name (see above); otelecho accepted any string. - Options become
Configfields:WithTracerProvider→TracerProvider,WithMeterProvider→MeterProvider,WithPropagators→Propagators,WithSkipper→Skipper,WithMetricAttributeFn/WithEchoMetricAttributeFn→MetricAttributes,WithOnError→OnNextError. - otelecho called the Echo error handler (
c.Error(err)) from the middleware; echo-otel does not. If you use a customHTTPErrorHandlerthat maps errors to other status codes, setOnNextError: func(c echo.Context, err error) { c.Error(err) }so the span reports the status code that was actually sent. - The instrumentation scope name changes to
github.com/labstack/echo-otel/v4, and theecho.errorspan attribute is replaced byerror.typeand the span status. Update dashboards and alerts that rely on them.
Middleware options
Use NewMiddlewareWithConfig with an echootel.Config to customize the middleware:
| Config field | Description |
|---|---|
ServerName | Server host name with an optional port, recorded as server.address/server.port |
Skipper | Skip tracing (return true to skip, false to trace) |
TracerProvider | Use a custom TracerProvider instead of the global one |
MeterProvider | Use a custom MeterProvider instead of the global one |
Propagators | Specify propagators for extracting trace context |
SpanStartOptions | Additional trace.SpanStartOptions applied to each new span |
SpanStartAttributes | Add span attributes when the span starts |
SpanEndAttributes | Add span attributes when the request completes |
MetricAttributes | Compose the attributes recorded on HTTP metrics |
Metrics | Replace the default metrics with your own MetricsRecorder |
OnNextError | Called with errors returned by the next middleware or handler |
OnExtractionError | Called when extracting request attributes fails |
Skipping health checks
Use the Skipper field to exclude certain endpoints from tracing:
import (
echootel "github.com/labstack/echo-otel/v4"
"github.com/labstack/echo/v4"
)
e := echo.New()
e.Use(echootel.NewMiddlewareWithConfig(echootel.Config{
ServerName: "api.example.com",
Skipper: func(c echo.Context) bool {
// Return true to skip, false to trace
return c.Path() == "/health" || c.Path() == "/ready"
},
}))
Custom tracer and meter providers
For more control over telemetry collection, you can specify custom providers:
import (
echootel "github.com/labstack/echo-otel/v4"
"github.com/labstack/echo/v4"
"go.opentelemetry.io/otel"
)
e := echo.New()
e.Use(echootel.NewMiddlewareWithConfig(echootel.Config{
ServerName: "api.example.com",
TracerProvider: otel.GetTracerProvider(),
MeterProvider: otel.GetMeterProvider(),
}))
Adding custom attributes
Use SpanStartAttributes and SpanEndAttributes to add attributes to spans, and MetricAttributes to add attributes to metrics:
import (
echootel "github.com/labstack/echo-otel/v4"
"github.com/labstack/echo/v4"
"go.opentelemetry.io/otel/attribute"
)
e := echo.New()
e.Use(echootel.NewMiddlewareWithConfig(echootel.Config{
ServerName: "api.example.com",
SpanEndAttributes: func(c echo.Context, v *echootel.Values, attr []attribute.KeyValue) []attribute.KeyValue {
// Append to attr and return it, otherwise the default attributes are dropped.
return append(attr, attribute.String("tenant.id", c.Request().Header.Get("X-Tenant-ID")))
},
MetricAttributes: func(c echo.Context, v *echootel.Values) []attribute.KeyValue {
// MetricAttributes replaces the default metric attributes, so start from v.MetricAttributes().
return append(v.MetricAttributes(), attribute.String("deployment.environment.name", "production"))
},
}))
Span callbacks must append to the attr argument and return it. Keep metric attributes low cardinality: values chosen by clients, such as IP addresses or user agents, belong on spans, not metrics.
HTTP metrics
The echo-otel middleware automatically collects the following HTTP server metrics:
| 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 |
These metrics follow the OpenTelemetry semantic conventions for HTTP and include the http.request.method, url.scheme, http.route, network.protocol.name, network.protocol.version, http.response.status_code, and error.type attributes.
Creating Custom Spans
Add custom spans to trace specific operations within your handlers:
import (
"net/http"
"github.com/labstack/echo/v4"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
)
var tracer = otel.Tracer("my-service")
func getUser(c echo.Context) error {
ctx := c.Request().Context()
// Create a custom span for the database query
ctx, span := tracer.Start(ctx, "fetch_user")
defer span.End()
userID := c.Param("id")
span.SetAttributes(attribute.String("user.id", userID))
user, err := fetchUserFromDB(ctx, userID)
if err != nil {
span.SetAttributes(attribute.Bool("user.found", false))
return c.JSON(http.StatusNotFound, map[string]string{"error": "User not found"})
}
span.SetAttributes(attribute.Bool("user.found", true))
return c.JSON(http.StatusOK, user)
}
Recording Errors
Record errors on spans to mark them as failed and capture error details:
import (
"net/http"
"github.com/labstack/echo/v4"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)
func processOrder(c echo.Context) error {
span := trace.SpanFromContext(c.Request().Context())
var order Order
if err := c.Bind(&order); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "invalid request body")
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
}
if err := validateOrder(order); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return c.JSON(http.StatusUnprocessableEntity, map[string]string{"error": err.Error()})
}
return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
}
When a handler returns an error, echo-otel sets the span status and the error.type attribute for you. 4xx responses are not treated as errors on server spans, and 5xx responses are.
FAQ
What versions of Echo are supported? Echo v4 with github.com/labstack/echo-otel/v4 (Echo v4.15.4 or later) and Echo v5 with github.com/labstack/echo-otel/v5 (Echo v5.2.1 or later). If you're still on Echo v3, upgrade to v4 first—the migration is straightforward and well-documented.
Is otelecho still supported? No. go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho is deprecated in favor of echo-otel. See Migrating from otelecho for the changes.
How do I pass context to downstream services? The echo-otel middleware automatically injects trace context into the request context. Use c.Request().Context() to retrieve it, then pass it to your database clients, HTTP clients, or gRPC calls to propagate tracing across services.
Why are my health check endpoints cluttering traces? Use the Skipper config field to exclude /health and /ready endpoints. This reduces noise and lowers telemetry costs without losing visibility into real traffic.
Can I use echo-otel with Echo's built-in middleware? Yes. Add middleware.Recover() first, then echootel.NewMiddleware, then the rest of your middleware (logging, auth, etc.) so that all downstream middleware and handlers are captured in the trace.
How do I add metrics alongside traces? The middleware automatically collects HTTP server metrics (http.server.request.duration, http.server.request.body.size, http.server.response.body.size). Configure a MeterProvider to export them.
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.
What's next?
With OpenTelemetry Echo 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
- Create custom spans using the OpenTelemetry Go Tracing API
- Explore other Go frameworks like Gin or Beego
- Set up the OpenTelemetry Collector for production deployments