Skip to content
Last9 named a Gartner Cool Vendor in AI for SRE Observability for 2025! Read more →
Last9

Beego

Learn how to integrate OpenTelemetry with Beego Go applications and send telemetry data to Last9

This guide shows you how to instrument your Beego v2 Go application with OpenTelemetry and send traces, metrics, and logs to Last9.

Prerequisites

  • Go 1.19 or later
  • Beego v2 application
  • Last9 account with OTLP endpoint configured

Installation

  1. Install OpenTelemetry packages

    go get -u go.opentelemetry.io/otel
    go get -u go.opentelemetry.io/otel/sdk
    go get -u go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
    go get -u go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc
    go get -u go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc
    go get -u go.opentelemetry.io/otel/sdk/metric
    go get -u go.opentelemetry.io/otel/sdk/log
    go get -u go.opentelemetry.io/otel/log/global
    go get -u go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
  2. Install Beego v2

    go get -u github.com/beego/beego/v2/server/web
  3. Install database instrumentation (optional)

    # For PostgreSQL
    go get -u go.nhat.io/otelsql
    go get -u github.com/lib/pq
    # For ORM integration
    go get -u github.com/beego/beego/v2/client/orm
  4. Install Redis instrumentation (optional)

    # For go-redis v9
    go get -u github.com/redis/go-redis/v9
    go get -u github.com/redis/go-redis/extra/redisotel/v9

Configuration

Set up your environment variables:

export OTEL_EXPORTER_OTLP_ENDPOINT="$last9_otlp_endpoint"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=$last9_otlp_auth_header"
export OTEL_SERVICE_NAME="beego-api"
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production,service.version=1.0.0"
export OTEL_TRACES_SAMPLER="always_on"
export OTEL_LOG_LEVEL="info"

Instrumentation Setup

Core Instrumentation

Create a comprehensive instrumentation setup:

package instrumentation
import (
"context"
"fmt"
"log"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/log/global"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/log"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
"go.opentelemetry.io/otel/trace"
)
type Instrumentation struct {
TracerProvider *sdktrace.TracerProvider
MeterProvider *sdkmetric.MeterProvider
LoggerProvider *log.LoggerProvider
Tracer trace.Tracer
Meter metric.Meter
Logger *log.Logger
}
func NewInstrumentation(serviceName string) (*Instrumentation, error) {
// Create resource with service information
res, err := resource.New(context.Background(),
resource.WithFromEnv(),
resource.WithTelemetrySDK(),
resource.WithAttributes(
semconv.ServiceNameKey.String(serviceName),
),
)
if err != nil {
return nil, fmt.Errorf("failed to create resource: %w", err)
}
// Initialize trace provider
traceProvider, err := initTracerProvider(res)
if err != nil {
return nil, fmt.Errorf("failed to initialize tracer provider: %w", err)
}
// Initialize metrics provider
metricsProvider, err := initMeterProvider(res)
if err != nil {
return nil, fmt.Errorf("failed to initialize meter provider: %w", err)
}
// Initialize log provider
logProvider, err := initLogProvider(res)
if err != nil {
return nil, fmt.Errorf("failed to initialize log provider: %w", err)
}
// Set global providers
otel.SetTracerProvider(traceProvider)
otel.SetMeterProvider(metricsProvider)
global.SetLoggerProvider(logProvider)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
))
tracer := traceProvider.Tracer(serviceName)
meter := metricsProvider.Meter(serviceName)
logger := logProvider.Logger(serviceName)
return &Instrumentation{
TracerProvider: traceProvider,
MeterProvider: metricsProvider,
LoggerProvider: logProvider,
Tracer: tracer,
Meter: meter,
Logger: &logger,
}, nil
}
func initTracerProvider(res *resource.Resource) (*sdktrace.TracerProvider, error) {
ctx := context.Background()
exporter, err := otlptracegrpc.New(ctx)
if err != nil {
return nil, fmt.Errorf("failed to create trace exporter: %w", err)
}
provider := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(res),
sdktrace.WithSampler(sdktrace.AlwaysSample()),
)
return provider, nil
}
func initMeterProvider(res *resource.Resource) (*sdkmetric.MeterProvider, error) {
ctx := context.Background()
exporter, err := otlpmetricgrpc.New(ctx)
if err != nil {
return nil, fmt.Errorf("failed to create metric exporter: %w", err)
}
provider := sdkmetric.NewMeterProvider(
sdkmetric.WithReader(
sdkmetric.NewPeriodicReader(exporter, sdkmetric.WithInterval(30*time.Second)),
),
sdkmetric.WithResource(res),
)
return provider, nil
}
func initLogProvider(res *resource.Resource) (*log.LoggerProvider, error) {
ctx := context.Background()
exporter, err := otlploggrpc.New(ctx)
if err != nil {
return nil, fmt.Errorf("failed to create log exporter: %w", err)
}
provider := log.NewLoggerProvider(
log.WithProcessor(log.NewBatchProcessor(exporter)),
log.WithResource(res),
)
return provider, nil
}
func (i *Instrumentation) Shutdown(ctx context.Context) error {
var errs []error
if err := i.TracerProvider.Shutdown(ctx); err != nil {
errs = append(errs, fmt.Errorf("failed to shutdown tracer provider: %w", err))
}
if err := i.MeterProvider.Shutdown(ctx); err != nil {
errs = append(errs, fmt.Errorf("failed to shutdown meter provider: %w", err))
}
if err := i.LoggerProvider.Shutdown(ctx); err != nil {
errs = append(errs, fmt.Errorf("failed to shutdown log provider: %w", err))
}
if len(errs) > 0 {
return fmt.Errorf("shutdown errors: %v", errs)
}
return nil
}

Main Application Setup

package main
import (
"context"
"log"
"os"
"os/signal"
"syscall"
"time"
beego "github.com/beego/beego/v2/server/web"
"your-app/controllers"
"your-app/instrumentation"
)
func main() {
// Initialize instrumentation
instr, err := instrumentation.NewInstrumentation("beego-api")
if err != nil {
log.Fatalf("Failed to initialize instrumentation: %v", err)
}
// Initialize middleware
middleware, err := instrumentation.NewBeegoMiddleware("beego-api")
if err != nil {
log.Fatalf("Failed to create middleware: %v", err)
}
// Setup graceful shutdown
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
<-sigCh
log.Println("Shutting down server...")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := instr.Shutdown(shutdownCtx); err != nil {
log.Printf("Error shutting down instrumentation: %v", err)
}
}()
// Register global filter for all requests (optional approach)
beego.InsertFilter("/*", beego.BeforeRouter, middleware.BeegoFilter)
// Setup routes
setupRoutes(instr, middleware)
// Configure Beego
beego.BConfig.WebConfig.DirectoryIndex = true
beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
// Start server
log.Println("Starting Beego server...")
beego.Run()
}
func setupRoutes(instr *instrumentation.Instrumentation, middleware *instrumentation.BeegoMiddleware) {
// Initialize controllers
userController := controllers.NewUserController(instr)
productController := controllers.NewProductController(instr)
// Register routes with controllers
// Method 1: Using wrapped handlers
beego.Router("/api/v1/users", &controllers.UserController{}, "get:ListUsers")
beego.Router("/api/v1/users", &controllers.UserController{}, "post:CreateUser")
beego.Router("/api/v1/users/:id", &controllers.UserController{}, "get:GetUser")
beego.Router("/api/v1/users/:id", &controllers.UserController{}, "put:UpdateUser")
beego.Router("/api/v1/users/:id", &controllers.UserController{}, "delete:DeleteUser")
// Product routes
beego.Router("/api/v1/products", &controllers.ProductController{}, "get:ListProducts")
beego.Router("/api/v1/products", &controllers.ProductController{}, "post:CreateProduct")
beego.Router("/api/v1/products/:id", &controllers.ProductController{}, "get:GetProduct")
// Health check
beego.Router("/health", &controllers.HealthController{}, "get:Check")
}

Database Integration

PostgreSQL with ORM

package models
import (
"database/sql"
"fmt"
"github.com/beego/beego/v2/client/orm"
"go.nhat.io/otelsql"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
_ "github.com/lib/pq"
)
// User model
type User struct {
ID int64 `orm:"auto" json:"id"`
Name string `orm:"size(100)" json:"name"`
Email string `orm:"size(100);unique" json:"email"`
}
func InitDatabase() error {
// Register the instrumented PostgreSQL driver
driverName, err := otelsql.Register("postgres",
otelsql.AllowRoot(),
otelsql.TraceQueryWithoutArgs(),
otelsql.TraceRowsClose(),
otelsql.TraceRowsAffected(),
otelsql.WithDatabaseName("beego_db"),
otelsql.WithSystem(semconv.DBSystemPostgreSQL),
)
if err != nil {
return fmt.Errorf("failed to register driver: %w", err)
}
// Configure Beego ORM to use the instrumented driver
dataSource := "user=dbuser dbname=beego_db password=dbpass host=localhost port=5432 sslmode=disable"
// Register database with instrumented driver
err = orm.RegisterDataBase("default", driverName, dataSource)
if err != nil {
return fmt.Errorf("failed to register database: %w", err)
}
// Register models
orm.RegisterModel(new(User))
// Create tables if they don't exist
err = orm.RunSyncdb("default", false, true)
if err != nil {
return fmt.Errorf("failed to sync database: %w", err)
}
return nil
}
// GetDB returns the underlying sql.DB for metrics recording
func GetDB() (*sql.DB, error) {
return orm.GetDB("default")
}

Redis Integration

package cache
import (
"context"
"log"
"github.com/redis/go-redis/v9"
"github.com/redis/go-redis/extra/redisotel/v9"
)
type RedisClient struct {
client *redis.Client
}
func NewRedisClient() *RedisClient {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
// Enable tracing instrumentation
if err := redisotel.InstrumentTracing(rdb); err != nil {
log.Fatalf("Failed to instrument Redis tracing: %v", err)
}
// Enable metrics instrumentation
if err := redisotel.InstrumentMetrics(rdb); err != nil {
log.Fatalf("Failed to instrument Redis metrics: %v", err)
}
return &RedisClient{client: rdb}
}
func (r *RedisClient) Get(ctx context.Context, key string) (string, error) {
val, err := r.client.Get(ctx, key).Result()
if err == redis.Nil {
return "", nil // Key does not exist
} else if err != nil {
return "", err
}
return val, nil
}
func (r *RedisClient) Set(ctx context.Context, key, value string, expiration time.Duration) error {
return r.client.Set(ctx, key, value, expiration).Err()
}
func (r *RedisClient) Delete(ctx context.Context, key string) error {
return r.client.Del(ctx, key).Err()
}
func (r *RedisClient) Close() error {
return r.client.Close()
}

HTTP Client Instrumentation

Outgoing HTTP Requests

package controllers
import (
"encoding/json"
"io"
"net/http"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel/attribute"
)
// ExternalAPIController handles external API calls
type ExternalAPIController struct {
BaseController
httpClient *http.Client
}
func NewExternalAPIController(instr *instrumentation.Instrumentation) *ExternalAPIController {
return &ExternalAPIController{
BaseController: *NewBaseController(instr),
httpClient: &http.Client{
Transport: otelhttp.NewTransport(http.DefaultTransport),
},
}
}
// GetRandomJoke handles GET /api/v1/jokes/random
func (c *ExternalAPIController) GetRandomJoke() {
span := c.GetSpan()
span.SetAttributes(attribute.String("operation", "get_random_joke"))
// Make request with instrumentation
ctx := c.GetSpanContext()
req, err := http.NewRequestWithContext(ctx, "GET", "https://official-joke-api.appspot.com/random_joke", nil)
if err != nil {
span.RecordError(err)
c.SetErrorResponse(500, "Failed to create request")
return
}
resp, err := c.httpClient.Do(req)
if err != nil {
span.RecordError(err)
c.SetErrorResponse(500, "Failed to fetch joke")
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
span.RecordError(err)
c.SetErrorResponse(500, "Failed to read response")
return
}
var joke map[string]interface{}
if err := json.Unmarshal(body, &joke); err != nil {
span.RecordError(err)
c.SetErrorResponse(500, "Failed to parse response")
return
}
c.SetJSONResponse(200, joke)
}

Production Deployment

Docker

FROM golang:1.21-alpine AS builder
WORKDIR /app
# Install build dependencies
RUN apk add --no-cache git
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build the application
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o beego-app .
FROM alpine:latest
# Install runtime dependencies
RUN apk --no-cache add ca-certificates curl
WORKDIR /root/
# Copy the binary
COPY --from=builder /app/beego-app .
# Copy configuration files if any
COPY --from=builder /app/conf ./conf
# Expose port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
CMD ["./beego-app"]

Kubernetes

apiVersion: apps/v1
kind: Deployment
metadata:
name: beego-api
labels:
app: beego-api
spec:
replicas: 3
selector:
matchLabels:
app: beego-api
template:
metadata:
labels:
app: beego-api
spec:
containers:
- name: beego-api
image: your-registry/beego-api:latest
ports:
- containerPort: 8080
env:
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "$last9_otlp_endpoint"
- name: OTEL_EXPORTER_OTLP_HEADERS
value: "Authorization=$last9_otlp_auth_header"
- name: OTEL_SERVICE_NAME
value: "beego-api"
- name: OTEL_RESOURCE_ATTRIBUTES
value: "deployment.environment=production,service.version=1.0.0,k8s.cluster.name=production"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: beego-secrets
key: database-url
- name: REDIS_URL
valueFrom:
configMapKeyRef:
name: beego-config
key: redis-url
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: beego-api-service
spec:
selector:
app: beego-api
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer

Testing the Integration

Test your Beego application:

  1. Start the application

    go run main.go
  2. Make test requests

    # Health check
    curl http://localhost:8080/health
    # User endpoints
    curl http://localhost:8080/api/v1/users
    curl http://localhost:8080/api/v1/users/1
    # Create a user
    curl -X POST http://localhost:8080/api/v1/users \
    -H "Content-Type: application/json" \
    -d '{"name": "Test User", "email": "test@example.com"}'
  3. View telemetry in Last9

    Check your Last9 dashboard for:

    • HTTP request traces with proper context propagation
    • Database operation spans with query details
    • Redis operation traces
    • HTTP client spans for external API calls
    • Custom business logic spans