This guide shows you how to instrument your Go application with OpenTelemetry and send traces to SigNoz. Instrument your Go services with the OpenTelemetry Go SDK and send traces to SigNoz Cloud or a self-hosted collector.
Prerequisites
- Supported Go version
- A SigNoz Cloud account or self-hosted SigNoz instance
- Your application code
Send traces to SigNoz
Step 1. Set environment variables
Set the following environment variables to configure the OpenTelemetry exporter:
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
export OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
export OTEL_SERVICE_NAME="<service-name>"
export OTEL_RESOURCE_ATTRIBUTES="service.version=<service-version>"
Verify these values:
<region>: Your SigNoz Cloud region<your-ingestion-key>: Your SigNoz ingestion key<service-name>: A descriptive name for your service (e.g.,payment-service)<service-version>(optional): Your release version, image tag, or git SHA (e.g.,1.4.2,a01dbef8).
Set service.version to a per-build value, not a static string. SigNoz detects a deployment each time this value changes. Common sources:
- Bash / shell:
service.version=$(git rev-parse --short HEAD) - GitHub Actions:
service.version=${{ github.sha }} - GitLab CI:
service.version=$CI_COMMIT_SHORT_SHA - Kubernetes: inject from your Helm chart image tag or CI variable
Step 1. Set environment variables
Add these environment variables to your deployment manifest:
env:
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: 'https://ingest.<region>.signoz.cloud:443'
- name: OTEL_EXPORTER_OTLP_HEADERS
value: 'signoz-ingestion-key=<your-ingestion-key>'
- name: OTEL_SERVICE_NAME
value: '<service-name>'
- name: OTEL_RESOURCE_ATTRIBUTES
value: 'service.version=<service-version>'
Verify these values:
<region>: Your SigNoz Cloud region<your-ingestion-key>: Your SigNoz ingestion key<service-name>: A descriptive name for your service (e.g.,payment-service)<service-version>(optional): Your release version, image tag, or git SHA (e.g.,1.4.2,a01dbef8).
Set service.version to a per-build value, not a static string. SigNoz detects a deployment each time this value changes. Common sources:
- Bash / shell:
service.version=$(git rev-parse --short HEAD) - GitHub Actions:
service.version=${{ github.sha }} - GitLab CI:
service.version=$CI_COMMIT_SHORT_SHA - Kubernetes: inject from your Helm chart image tag or CI variable
Step 1. Set environment variables (PowerShell)
$env:OTEL_EXPORTER_OTLP_ENDPOINT = "https://ingest.<region>.signoz.cloud:443"
$env:OTEL_EXPORTER_OTLP_HEADERS = "signoz-ingestion-key=<your-ingestion-key>"
$env:OTEL_SERVICE_NAME = "<service-name>"
$env:OTEL_RESOURCE_ATTRIBUTES = "service.version=<service-version>"
Verify these values:
<region>: Your SigNoz Cloud region<your-ingestion-key>: Your SigNoz ingestion key<service-name>: A descriptive name for your service<service-version>(optional): Your release version, image tag, or git SHA (e.g.,1.4.2,a01dbef8).
Set service.version to a per-build value, not a static string. SigNoz detects a deployment each time this value changes. Common sources:
- Bash / shell:
service.version=$(git rev-parse --short HEAD) - GitHub Actions:
service.version=${{ github.sha }} - GitLab CI:
service.version=$CI_COMMIT_SHORT_SHA - Kubernetes: inject from your Helm chart image tag or CI variable
Step 1. Set environment variables in Dockerfile
Add environment variables to your Dockerfile:
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o main .
FROM alpine:latest
WORKDIR /app
COPY /app/main .
# Set OpenTelemetry environment variables
ENV OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
ENV OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
ENV OTEL_SERVICE_NAME="<service-name>"
ENV OTEL_RESOURCE_ATTRIBUTES="service.version=<service-version>"
CMD ["./main"]
Or pass them at runtime using docker run:
docker run -e OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443" \
-e OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>" \
-e OTEL_SERVICE_NAME="<service-name>" \
-e OTEL_RESOURCE_ATTRIBUTES="service.version=<service-version>" \
your-image:latest
Verify these values:
<region>: Your SigNoz Cloud region<your-ingestion-key>: Your SigNoz ingestion key<service-name>: A descriptive name for your service (e.g.,payment-service)<service-version>(optional): Your release version, image tag, or git SHA (e.g.,1.4.2,a01dbef8).
Set service.version to a per-build value, not a static string. SigNoz detects a deployment each time this value changes. Common sources:
- Bash / shell:
service.version=$(git rev-parse --short HEAD) - GitHub Actions:
service.version=${{ github.sha }} - GitLab CI:
service.version=$CI_COMMIT_SHORT_SHA - Kubernetes: inject from your Helm chart image tag or CI variable
Step 2. Install OpenTelemetry packages
Run the following command in your project directory:
go get \
go.opentelemetry.io/otel \
go.opentelemetry.io/otel/sdk \
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp \
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
Step 3. Create the tracer initialization
Create a file named tracing.go in your project:
package main
import (
"context"
"log"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
)
func initTracer(ctx context.Context) (func(context.Context) error, error) {
// Reads OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS from environment
exporter, err := otlptracehttp.New(ctx)
if err != nil {
return nil, err
}
// Reads OTEL_SERVICE_NAME from environment and adds host/process/OS attributes
res, err := resource.New(ctx,
resource.WithFromEnv(),
resource.WithHost(),
resource.WithOS(),
resource.WithProcess(),
)
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(res),
)
// Makes the tracer available to instrumentation libraries
otel.SetTracerProvider(tp)
// Propagates trace context across service boundaries using W3C standards
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
))
return tp.Shutdown, nil
}
Step 4. Instrument your application
Here's a complete example using net/http with automatic instrumentation:
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
shutdown, err := initTracer(ctx)
if err != nil {
log.Fatalf("Failed to initialize tracer: %v", err)
}
defer shutdown(ctx)
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello from instrumented server!")
})
// Wrap with otelhttp middleware for automatic instrumentation
handler := otelhttp.NewHandler(mux, "my-service")
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", handler))
}
Library instrumentation
Choose your Go framework or library to add automatic instrumentation without leaving this page. Use the categories below to pick a specific integration.
For framework-specific setups, our guides on instrumenting a Gin application and gRPC services in Go show the common patterns beyond the base SDK.
This guide shows you how to add OpenTelemetry instrumentation to GORM. Complete the core Go instrumentation setup first.
Install GORM OpenTelemetry plugin
go get gorm.io/gorm
go get gorm.io/plugin/opentelemetry/tracing
Configure GORM with tracing
import (
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/plugin/opentelemetry/tracing"
)
func initDB() (*gorm.DB, error) {
dsn := "host=localhost user=postgres password=secret dbname=myapp port=5432"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
return nil, err
}
// Add OpenTelemetry tracing plugin to instrument all operations
if err := db.Use(tracing.NewPlugin()); err != nil {
return nil, err
}
return db, nil
}
// Use WithContext to propagate trace context
db.WithContext(ctx).Where("id = ?", userID).First(&user)
Tested with:
- Go 1.25.1
- OpenTelemetry Go SDK v1.38.0
- gorm.io/gorm v1.31.1
- gorm.io/plugin/opentelemetry v0.1.16
This guide shows you how to add OpenTelemetry instrumentation to database/sql. Complete the core Go instrumentation setup first.
Install database/sql instrumentation
go get go.opentelemetry.io/contrib/instrumentation/database/sql/otelsql
Register instrumented driver
import (
"context"
"database/sql"
_ "github.com/lib/pq"
"go.opentelemetry.io/contrib/instrumentation/database/sql/otelsql"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
)
// Open an instrumented database connection
db, err := otelsql.Open("postgres", dsn, otelsql.WithAttributes(
attribute.String("db.system", "postgresql"),
))
// All operations are automatically traced when using context
rows, err := db.QueryContext(ctx, "SELECT * FROM users WHERE id = $1", userID)
Adding manual child spans
tracer := otel.Tracer("my-service")
ctx, span := tracer.Start(ctx, "fetch-user-details")
defer span.End()
span.SetAttributes(attribute.String("user.id", userID))
rows, err := db.QueryContext(ctx, "SELECT * FROM users WHERE id = $1", userID)
Tested with:
- Go 1.25.1
- OpenTelemetry Go SDK v1.38.0
- go.opentelemetry.io/contrib/instrumentation/database/sql/otelsql v0.63.0
This guide shows you how to add OpenTelemetry instrumentation to MongoDB driver. Complete the core Go instrumentation setup first.
Install MongoDB instrumentation
go get go.mongodb.org/mongo-driver/mongo
go get go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo
Instrument MongoDB client
import (
"context"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo"
)
clientOptions := options.Client().ApplyURI("mongodb://localhost:27017")
// Add OpenTelemetry monitor to trace all MongoDB operations
clientOptions.SetMonitor(otelmongo.NewMonitor())
client, err := mongo.Connect(ctx, clientOptions)
if err != nil {
log.Fatal(err)
}
// All operations are automatically traced
collection := client.Database("mydb").Collection("users")
result, err := collection.FindOne(ctx, bson.M{"email": email})
Tested with:
- Go 1.25.1
- OpenTelemetry Go SDK v1.38.0
- go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.63.0
This guide shows you how to add OpenTelemetry instrumentation to Redis. Complete the core Go instrumentation setup first.
Install Redis instrumentation
go get github.com/redis/go-redis/v9
go get go.opentelemetry.io/contrib/instrumentation/github.com/redis/go-redis/v9/otelredis
Instrument Redis client
import (
"context"
"github.com/redis/go-redis/v9"
"go.opentelemetry.io/contrib/instrumentation/github.com/redis/go-redis/v9/otelredis"
"go.opentelemetry.io/otel/attribute"
)
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
// Add OpenTelemetry instrumentation to trace all Redis commands
if err := otelredis.InstrumentTracing(rdb,
otelredis.WithAttributes(
attribute.String("db.system", "redis"),
),
); err != nil {
log.Fatal(err)
}
// All operations are automatically traced
val, err := rdb.Get(ctx, "key").Result()
Tested with:
- Go 1.25.1
- OpenTelemetry Go SDK v1.38.0
- go.opentelemetry.io/contrib/instrumentation/github.com/redis/go-redis/v9/otelredis v0.63.0
This guide shows you how to add OpenTelemetry instrumentation to net/http (standard library). Complete the core Go instrumentation setup first.
Install net/http instrumentation
go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
Complete example
Full working example using standard net/http:
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
// Use the initTracer function from Step 3 above
func initTracer(ctx context.Context) (func(context.Context) error, error) {
// Paste the complete initTracer implementation from the core setup section
return nil, nil // Placeholder - replace with actual implementation
}
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
shutdown, err := initTracer(ctx)
if err != nil {
log.Fatal(err)
}
defer shutdown(ctx)
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Extract the span created by otelhttp middleware
span := trace.SpanFromContext(r.Context())
// Add custom attributes to the span
span.SetAttributes(attribute.String("custom.key", "value"))
fmt.Fprint(w, "Hello net/http with tracing!")
})
// Wrap with otelhttp middleware to create spans automatically
handler := otelhttp.NewHandler(mux, "net-http-server")
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", handler))
}
Adding manual spans
Create custom child spans to trace specific operations within your handlers:
mux.HandleFunc("/manual", func(w http.ResponseWriter, r *http.Request) {
tracer := otel.Tracer("my-service")
// Create a child span (parent created by otelhttp middleware)
ctx, span := tracer.Start(r.Context(), "custom-operation")
defer span.End()
span.SetAttributes(attribute.String("manual.key", "value"))
span.AddEvent("manual.event")
// Use ctx for downstream calls to propagate the trace
fmt.Fprint(w, "Manual tracing!")
})
Tested with:
- Go 1.25.1
- OpenTelemetry Go SDK v1.38.0
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
This guide shows you how to add OpenTelemetry instrumentation to a Gin application. Complete the core Go instrumentation setup first.
Install Gin instrumentation
go get github.com/gin-gonic/gin
go get go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin
Complete example
package main
import (
"context"
"log"
"os"
"os/signal"
"github.com/gin-gonic/gin"
"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
// Use the initTracer function from Step 3 above
func initTracer(ctx context.Context) (func(context.Context) error, error) {
// Paste the complete initTracer implementation from the core setup section
return nil, nil // Placeholder - replace with actual implementation
}
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
shutdown, err := initTracer(ctx)
if err != nil {
log.Fatal(err)
}
defer shutdown(ctx)
r := gin.Default()
// Add OpenTelemetry middleware to instrument all routes
r.Use(otelgin.Middleware("my-gin-service"))
r.GET("/", func(c *gin.Context) {
// Extract the span created by otelgin middleware
ctx := c.Request.Context()
span := trace.SpanFromContext(ctx)
span.SetAttributes(attribute.String("custom.key", "value"))
span.AddEvent("manual.event")
c.JSON(200, gin.H{"message": "Hello traced Gin!"})
})
log.Println("Server starting on :8080")
log.Fatal(r.Run(":8080"))
}
Adding manual child spans
Create additional spans to trace specific operations within your handlers:
r.GET("/process", func(c *gin.Context) {
tracer := otel.Tracer("my-service")
// Create a child span (parent created by otelgin middleware)
ctx, span := tracer.Start(c.Request.Context(), "process-data")
defer span.End()
span.SetAttributes(attribute.String("operation", "data-processing"))
// Use ctx for downstream calls to propagate trace context
c.JSON(200, gin.H{"status": "processed"})
})
Tested with:
- Go 1.25.1
- OpenTelemetry Go SDK v1.38.0
- go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.63.0
This guide shows you how to add OpenTelemetry instrumentation to an Echo application. Complete the core Go instrumentation setup first.
Install Echo instrumentation
go get github.com/labstack/echo/v4
go get go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/v4/otelecho
Complete example
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/v4/otelecho"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
// Use the initTracer function from Step 3 above
func initTracer(ctx context.Context) (func(context.Context) error, error) {
// Paste the complete initTracer implementation from the core setup section
return nil, nil // Placeholder - replace with actual implementation
}
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
shutdown, err := initTracer(ctx)
if err != nil {
log.Fatal(err)
}
defer shutdown(ctx)
e := echo.New()
e.Use(middleware.Recover())
// Add OpenTelemetry middleware to instrument all routes
e.Use(otelecho.Middleware("my-echo-service"))
e.GET("/", func(c echo.Context) error {
// Extract the span created by otelecho middleware
span := trace.SpanFromContext(c.Request().Context())
span.SetAttributes(attribute.String("custom.key", "value"))
span.AddEvent("manual.event")
return c.String(http.StatusOK, "Hello traced Echo!")
})
log.Println("Server starting on :8080")
log.Fatal(e.Start(":8080"))
}
Adding manual child spans
Create additional spans to trace specific operations within your handlers:
e.GET("/process", func(c echo.Context) error {
tracer := otel.Tracer("my-service")
// Create a child span (parent created by otelecho middleware)
ctx, span := tracer.Start(c.Request().Context(), "process-data")
defer span.End()
span.SetAttributes(attribute.String("operation", "data-processing"))
// Use ctx for downstream calls to propagate trace context
return c.JSON(http.StatusOK, map[string]string{"status": "processed"})
})
Tested with:
- Go 1.25.1
- OpenTelemetry Go SDK v1.38.0
- go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/v4/otelecho v0.63.0
This guide shows you how to add OpenTelemetry instrumentation to a Fiber application. Complete the core Go instrumentation setup first.
Install Fiber instrumentation
go get github.com/gofiber/fiber/v3
go get github.com/gofiber/contrib/v3/otel
Complete example
package main
import (
"context"
"log"
"os"
"os/signal"
fiberotel "github.com/gofiber/contrib/v3/otel"
"github.com/gofiber/fiber/v3"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
// Use the initTracer function from Step 3 above
func initTracer(ctx context.Context) (func(context.Context) error, error) {
// Paste the complete initTracer implementation from the core setup section
return nil, nil // Placeholder - replace with actual implementation
}
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
shutdown, err := initTracer(ctx)
if err != nil {
log.Fatal(err)
}
defer shutdown(ctx)
app := fiber.New()
// Add OpenTelemetry middleware to instrument all routes
app.Use(fiberotel.Middleware())
app.Get("/", func(c fiber.Ctx) error {
// Important: Use Context() for Fiber (fasthttp-based)
span := trace.SpanFromContext(c.Context())
span.SetAttributes(attribute.String("custom.key", "value"))
span.AddEvent("manual.event")
return c.SendString("Hello traced Fiber!")
})
log.Println("Server starting on :8080")
log.Fatal(app.Listen(":8080"))
}
Adding manual child spans
Create additional spans to trace specific operations within your handlers:
app.Get("/process", func(c fiber.Ctx) error {
tracer := otel.Tracer("my-service")
// Create a child span (parent created by otelfiber middleware)
// Important: Use Context() for Fiber (fasthttp-based)
ctx, span := tracer.Start(c.Context(), "process-data")
defer span.End()
span.SetAttributes(attribute.String("operation", "data-processing"))
// Use ctx for downstream calls to propagate trace context
return c.JSON(fiber.Map{"status": "processed"})
})
Tested with:
- Go 1.25.6
- OpenTelemetry Go SDK v1.40.0
- github.com/gofiber/contrib/v3/otel v1.0.0
This guide shows you how to add OpenTelemetry instrumentation to a Chi application. Complete the core Go instrumentation setup first.
Install Chi instrumentation
go get github.com/go-chi/chi/v5
go get github.com/riandyrn/otelchi
Complete example
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/riandyrn/otelchi"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
// Use the initTracer function from Step 3 above
func initTracer(ctx context.Context) (func(context.Context) error, error) {
// Paste the complete initTracer implementation from the core setup section
return nil, nil // Placeholder - replace with actual implementation
}
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
shutdown, err := initTracer(ctx)
if err != nil {
log.Fatal(err)
}
defer shutdown(ctx)
r := chi.NewRouter()
r.Use(middleware.Recoverer)
r.Use(middleware.Logger)
// Add OpenTelemetry middleware to instrument all routes
// WithChiRoutes enables better route matching for span names
r.Use(otelchi.Middleware("my-chi-service", otelchi.WithChiRoutes(r)))
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
// Extract the span created by otelchi middleware
span := trace.SpanFromContext(r.Context())
span.SetAttributes(attribute.String("custom.key", "value"))
span.AddEvent("manual.event")
w.Write([]byte("Hello traced Chi!"))
})
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", r))
}
Adding manual child spans
Create additional spans to trace specific operations within your handlers:
r.Get("/process", func(w http.ResponseWriter, r *http.Request) {
tracer := otel.Tracer("my-service")
// Create a child span (parent created by otelchi middleware)
ctx, span := tracer.Start(r.Context(), "process-data")
defer span.End()
span.SetAttributes(attribute.String("operation", "data-processing"))
// Use ctx for downstream calls to propagate trace context
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"status": "processed"}`))
})
Tested with:
- Go 1.25.1
- OpenTelemetry Go SDK v1.38.0
- github.com/riandyrn/otelchi v0.12.2
This guide shows you how to add OpenTelemetry instrumentation to a Gorilla Mux application. Complete the core Go instrumentation setup first.
Install Gorilla instrumentation
go get github.com/gorilla/mux
go get go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux
Complete example
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"github.com/gorilla/mux"
"go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
// Use the initTracer function from Step 3 above
func initTracer(ctx context.Context) (func(context.Context) error, error) {
// Paste the complete initTracer implementation from the core setup section
return nil, nil // Placeholder - replace with actual implementation
}
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
shutdown, err := initTracer(ctx)
if err != nil {
log.Fatal(err)
}
defer shutdown(ctx)
r := mux.NewRouter()
// Add OpenTelemetry middleware to instrument all routes
r.Use(otelmux.Middleware("my-gorilla-service"))
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Extract the span created by otelmux middleware
span := trace.SpanFromContext(r.Context())
span.SetAttributes(attribute.String("custom.key", "value"))
span.AddEvent("manual.event")
w.Write([]byte("Hello traced Gorilla!"))
})
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", r))
}
Adding manual child spans
Create additional spans to trace specific operations within your handlers:
r.HandleFunc("/process", func(w http.ResponseWriter, r *http.Request) {
tracer := otel.Tracer("my-service")
// Create a child span (parent created by otelmux middleware)
ctx, span := tracer.Start(r.Context(), "process-data")
defer span.End()
span.SetAttributes(attribute.String("operation", "data-processing"))
// Use ctx for downstream calls to propagate trace context
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"status": "processed"}`))
})
Tested with:
- Go 1.25.1
- OpenTelemetry Go SDK v1.38.0
- go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.63.0
This guide shows you how to add OpenTelemetry instrumentation to a gRPC server. Complete the core Go instrumentation setup first.
Install gRPC instrumentation
go get google.golang.org/grpc
go get go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
Add server interceptors
import (
"google.golang.org/grpc"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
)
// Create gRPC server with OpenTelemetry instrumentation
s := grpc.NewServer(
grpc.StatsHandler(otelgrpc.NewServerHandler()),
)
// Register your gRPC services
// pb.RegisterYourServiceServer(s, &yourServer{})
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
log.Println("gRPC server starting on :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
Tested with:
- Go 1.25.1
- OpenTelemetry Go SDK v1.38.0
- go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0
- google.golang.org/grpc v1.77.0
This guide shows you how to add OpenTelemetry instrumentation to a gRPC client. Complete the core Go instrumentation setup first.
Install gRPC instrumentation
go get google.golang.org/grpc
go get go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
Add client interceptors
import (
"context"
"google.golang.org/grpc"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
)
// Create gRPC client with OpenTelemetry instrumentation
conn, err := grpc.NewClient(
"localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
)
if err != nil {
log.Fatalf("failed to connect: %v", err)
}
defer conn.Close()
// Create your gRPC client
// client := pb.NewYourServiceClient(conn)
// All RPC calls are automatically traced
// resp, err := client.YourMethod(ctx, &pb.YourRequest{})
Tested with:
- Go 1.25.1
- OpenTelemetry Go SDK v1.38.0
- go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0
- google.golang.org/grpc v1.77.0
Validate
After running your instrumented application, verify traces appear in SigNoz:
- Generate some traffic by making requests to your application.
- Open SigNoz and navigate to Traces.
- Click Refresh and look for new trace entries from your application.

- Click on any trace to view detailed span information and timing.

Troubleshooting
Why don't traces appear in SigNoz?
Check environment variables are set:
echo $OTEL_EXPORTER_OTLP_ENDPOINT
echo $OTEL_SERVICE_NAME
Verify network connectivity:
# For SigNoz Cloud
curl -v https://ingest.<region>.signoz.cloud:443/v1/traces
Why do OTLP exports fail with connection refused?
- VM: Verify the endpoint URL and that your firewall allows outbound HTTPS
- Kubernetes: Ensure the OTel Collector service is running and accessible
- Self-hosted: Confirm the collector is listening on the expected port
Why do spans go missing for specific requests?
Ensure you're using instrumented versions of HTTP clients and database drivers. Check if you have not set a sampling rate type which might affect sending spans or span rate.
Setup OpenTelemetry Collector (Optional)
What is the OpenTelemetry Collector?
Think of the OTel Collector as a middleman between your app and SigNoz. Instead of your application sending data directly to SigNoz, it sends everything to the Collector first, which then forwards it along.
Why use it?
- Cleaning up data — Filter out noisy traces you don't care about, or remove sensitive info before it leaves your servers.
- Keeping your app lightweight — Let the Collector handle batching, retries, and compression instead of your application code.
- Adding context automatically — The Collector can tag your data with useful info like which Kubernetes pod or cloud region it came from.
- Future flexibility — Want to send data to multiple backends later? The Collector makes that easy without changing your app.
See Switch from direct export to Collector for step-by-step instructions to convert your setup.
For more details, see Why use the OpenTelemetry Collector? and the Collector configuration guide.
Next steps
- Send logs from your Go application using popular logging libraries: Logrus, Zap, or Zerolog
- Correlate traces with logs to accelerate triage across signals
- Set up alerts for your Go application
- Create dashboards to visualize metrics
- Need to create custom spans or add attributes yourself? Use the Manual Instrumentation in Go guide once the base setup is in place.