OpenTelemetry Beego monitoring [otelhttp]
OpenTelemetry Beego instrumentation allows developers to monitor and diagnose issues with their Beego applications, providing valuable insights into application behavior in production.
Quick Setup
| Step | Action | Code/Command |
|---|---|---|
| 1. Install | Install Beego v2 and otelhttp | go get github.com/beego/beego/v2 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp |
| 2. Import | Import otelhttp | import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" |
| 3. Instrument | Run Beego with the otelhttp middleware | web.RunWithMiddleWares(":8080", otelhttp.NewMiddleware("my-service")) |
| 4. Verify | Check your observability backend for traces | Traces collected automatically |
Minimal working example:
import (
"github.com/beego/beego/v2/server/web"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
func main() {
web.Router("/", &MainController{})
web.RunWithMiddleWares(":8080", otelhttp.NewMiddleware("my-service"))
}
This single middleware call automatically captures all incoming HTTP requests, traces request flow, and exports telemetry data to your configured backend.
What is Beego?
Beego is an open-source web framework written in Go that follows the Model-View-Controller (MVC) architectural pattern. It provides a comprehensive set of tools for building web applications and APIs.
Beego's key features include:
- Full MVC architecture with auto-routing
- Built-in ORM for database operations
- Session management and caching
- Logging, configuration, and task scheduling modules
- Hot-reload during development
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 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.
Beego instrumentation
Beego v2 does not ship an OpenTelemetry filter, but it can run with any standard net/http middleware. Beego's web.MiddleWare type is simply func(http.Handler) http.Handler, so the otelhttp middleware from OpenTelemetry Go Contrib plugs in directly. It captures HTTP request details, timing information, and error states without requiring manual instrumentation.
To install Beego v2 and otelhttp instrumentation:
go get github.com/beego/beego/v2
go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
Migrating from otelbeego
The otelbeego package (go.opentelemetry.io/contrib/instrumentation/github.com/astaxie/beego/otelbeego) was built for Beego v1 and is no longer supported upstream. There is no Beego-specific replacement in OpenTelemetry Go Contrib, so the recommended approach is Beego v2 with otelhttp:
| Beego v1 + otelbeego | Beego v2 + otelhttp |
|---|---|
github.com/astaxie/beego | github.com/beego/beego/v2/server/web |
beego.Router(...), beego.Controller | web.Router(...), web.Controller |
beego.BConfig | web.BConfig |
otelbeego.NewOTelBeegoMiddleWare("my-service", opts...) | otelhttp.NewMiddleware("my-service", opts...) |
beego.RunWithMiddleWares(":8080", mware) | web.RunWithMiddleWares(":8080", mware) |
otelbeego.WithTracerProvider, WithMeterProvider, WithPropagators, WithFilter | otelhttp.WithTracerProvider, WithMeterProvider, WithPropagators, WithFilter |
otelbeego.Render, RenderBytes, RenderString | Manual span around c.Render(), see below |
Usage
You can instrument Beego by creating an otelhttp middleware and passing it to RunWithMiddleWares:
import (
"github.com/beego/beego/v2/server/web"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
func main() {
web.Router("/api/users", &UserController{})
web.Router("/api/orders", &OrderController{})
web.RunWithMiddleWares(":8080", otelhttp.NewMiddleware("my-service"))
}
Once instrumented, otelhttp automatically creates a server span for each incoming HTTP request, capturing method, URL, status code, request/response sizes, and timing information.
Because Beego does its own routing, otelhttp does not know which route matched the request: spans are named after the HTTP method only (for example, GET) and don't have the http.route attribute. See Recording the route to add it.
Middleware options
The otelhttp 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 |
WithFilter | Filter requests (return true to trace, false to skip) |
WithSpanNameFormatter | Customize span names, func(operation string, r *http.Request) string |
WithPublicEndpointFn | Start a new trace (linked to the incoming one) for public endpoints |
WithServerName | Set the name of the (virtual) server handling requests |
Filtering health checks
Use the WithFilter option to exclude certain endpoints from tracing:
import (
"net/http"
"github.com/beego/beego/v2/server/web"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
mware := otelhttp.NewMiddleware("my-service",
otelhttp.WithFilter(func(r *http.Request) bool {
// Return true to trace, false to skip
return r.URL.Path != "/health" && r.URL.Path != "/ready"
}),
)
web.RunWithMiddleWares(":8080", mware)
Custom tracer provider
For more control over telemetry collection, you can specify a custom provider:
import (
"github.com/beego/beego/v2/server/web"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
)
mware := otelhttp.NewMiddleware("my-service",
otelhttp.WithTracerProvider(otel.GetTracerProvider()),
otelhttp.WithMeterProvider(otel.GetMeterProvider()),
)
web.RunWithMiddleWares(":8080", mware)
Recording the route
Beego stores the matched route pattern (for example, /hello/:username) in the request context under the RouterPattern key before executing the controller. You can add a BeforeExec filter that copies it to the otelhttp span and metrics, so spans are named GET /hello/:username instead of just GET:
import (
"github.com/beego/beego/v2/server/web"
"github.com/beego/beego/v2/server/web/context"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
semconv "go.opentelemetry.io/otel/semconv/v1.43.0"
"go.opentelemetry.io/otel/trace"
)
func main() {
web.InsertFilter("*", web.BeforeExec, otelRouteFilter)
web.Router("/hello/:username", &HelloController{})
web.RunWithMiddleWares(":8080", otelhttp.NewMiddleware("my-service"))
}
func otelRouteFilter(ctx *context.Context) {
route, ok := ctx.Input.GetData("RouterPattern").(string)
if !ok || route == "" {
return
}
reqCtx := ctx.Request.Context()
// Rename the span and record the http.route attribute.
span := trace.SpanFromContext(reqCtx)
span.SetName(ctx.Request.Method + " " + route)
span.SetAttributes(semconv.HTTPRoute(route))
// Add http.route to the HTTP server metrics.
if labeler, ok := otelhttp.LabelerFromContext(reqCtx); ok {
labeler.Add(semconv.HTTPRoute(route))
}
}
Requests that don't match any route (404) and requests served by static files never reach BeforeExec, so they keep the default GET span name.
HTTP metrics
The otelhttp 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 attributes like method, scheme, and status code. The http.route attribute is only present if you add it as shown in Recording the route.
Instrumenting template rendering
otelhttp knows nothing about Beego templates, but you can trace rendering yourself. Disable autorender and wrap c.Render() in a span:
import (
"github.com/beego/beego/v2/server/web"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)
var tracer = otel.Tracer("my-service")
func init() {
web.BConfig.WebConfig.AutoRender = false
}
func (c *MainController) Get() {
c.TplName = "index.tpl"
if err := renderWithSpan(&c.Controller); err != nil {
c.Abort("500")
}
}
// renderWithSpan creates a child span for template rendering.
func renderWithSpan(c *web.Controller) error {
_, span := tracer.Start(c.Ctx.Request.Context(), "beego.render",
trace.WithAttributes(attribute.String("beego.template", c.TplName)))
defer span.End()
if err := c.Render(); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return err
}
return nil
}
The same approach works with c.RenderString() and c.RenderBytes(), which return the rendered template without sending the response.
Instrumenting outgoing HTTP requests
To trace outgoing HTTP calls from your Beego controllers, use otelhttp to wrap the http.Client transport. This creates client spans that link to the parent server span, giving you end-to-end visibility across service boundaries:
import (
"io"
"net/http"
"github.com/beego/beego/v2/server/web"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
// Create an instrumented HTTP client for outgoing requests
var httpClient = &http.Client{
Transport: otelhttp.NewTransport(http.DefaultTransport),
}
type ProxyController struct {
web.Controller
}
func (c *ProxyController) Get() {
ctx := c.Ctx.Request.Context()
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.example.com/data", nil)
if err != nil {
c.Abort("500")
return
}
resp, err := httpClient.Do(req)
if err != nil {
c.Abort("502")
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
c.Ctx.Output.Body(body)
}
Creating custom spans
In addition to automatic instrumentation, you can create custom spans to trace specific operations within your controllers:
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
)
var tracer = otel.Tracer("my-service")
func (c *UserController) Get() {
ctx := c.Ctx.Request.Context()
// Create a custom span for database operation
ctx, span := tracer.Start(ctx, "fetch-user-from-database")
defer span.End()
userID := c.Ctx.Input.Param(":id")
span.SetAttributes(attribute.String("user.id", userID))
user, err := db.FindUser(ctx, userID)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
c.Abort("500")
return
}
span.SetAttributes(attribute.Bool("user.found", user != nil))
c.Data["json"] = user
c.ServeJSON()
}
Error handling
otelhttp marks server spans as errors when the response status code is 5xx. Beego recovers panics in controllers by default (RecoverPanic) and responds with 500, so panicking requests show up as failed spans. You can also manually record errors in your controllers:
import (
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)
func (c *OrderController) Post() {
// Get the span from the request context
span := trace.SpanFromContext(c.Ctx.Request.Context())
order, err := processOrder(c.Ctx.Request.Context())
if err != nil {
// Record the error in the span
span.RecordError(err)
span.SetStatus(codes.Error, "order processing failed")
c.Abort("500")
return
}
c.Data["json"] = order
c.ServeJSON()
}
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
Is otelbeego still supported? No. The otelbeego package targets Beego v1 (github.com/astaxie/beego) and is no longer supported upstream. For Beego v2 (github.com/beego/beego/v2), use the generic otelhttp middleware with web.RunWithMiddleWares, as shown in this guide.
How do I filter health check endpoints? Use the WithFilter option when creating the middleware. Return false for paths like /health or /ready to skip tracing those requests. This reduces noise in your traces and lowers telemetry export costs.
Does otelhttp work with Beego's built-in filters? Yes. The otelhttp middleware wraps Beego's whole HTTP handler, so all Beego filters and controllers run inside the request span and can access it via trace.SpanFromContext(ctx.Request.Context()).
What's the performance impact of otelhttp? The performance impact is minimal — typically a few microseconds per request for span creation and context propagation. For high-traffic applications, configure sampling to control the volume of exported spans.
Can I use otelhttp with Beego's namespace routing? Yes, the middleware is applied at the server level via RunWithMiddleWares, so all routes including namespaced routes are automatically instrumented. otelhttp does not see Beego's route patterns, so use a BeforeExec filter as shown in Recording the route to set http.route.
How do I instrument Beego's ORM alongside HTTP tracing? Beego's ORM uses database/sql internally. Use otelsql to instrument the database driver, giving you database query spans that nest under your HTTP request spans for end-to-end visibility.
How do I pass context to downstream services? The otelhttp middleware injects trace context into the request. Use c.Ctx.Request.Context() to retrieve it, then pass it to your database clients, HTTP clients, or gRPC calls to propagate tracing across services.
What's next?
With OpenTelemetry Beego 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 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