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

Gin

Instrument Gin web applications with OpenTelemetry for comprehensive HTTP request tracing, database monitoring, and distributed system observability

Use OpenTelemetry to instrument your Go Gin web application and send telemetry data to Last9. This integration provides automatic instrumentation for HTTP requests, database operations, Redis commands, and external API calls in your Gin applications.

Gin is a popular HTTP web framework for Go that provides excellent performance and developer experience. With OpenTelemetry instrumentation, you can monitor request performance, trace distributed operations, and track system health across your Gin-based microservices.

Prerequisites

Before setting up Gin monitoring, ensure you have:

  • Go Development Environment: Go 1.19 or higher installed
  • Gin Application: Existing Gin web application to instrument
  • Last9 Account: With OpenTelemetry integration credentials
  • Module Support: Go modules enabled (go mod init if not already done)
  1. Install OpenTelemetry Packages

    Install the required OpenTelemetry packages for comprehensive Gin instrumentation:

    # Core OpenTelemetry packages
    go get go.opentelemetry.io/otel@latest
    go get go.opentelemetry.io/otel/sdk@latest
    go get go.opentelemetry.io/otel/trace@latest
    go get go.opentelemetry.io/otel/sdk/metric@latest
    # OTLP exporters for Last9
    go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp@latest
    go get go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc@latest
    # Gin-specific instrumentation
    go get go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin@latest
    # Additional instrumentation packages
    go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@latest
    go get github.com/redis/go-redis/extra/redisotel/v9@latest
    go get go.nhat.io/otelsql@latest

    These packages provide:

    • Core OTEL: Base OpenTelemetry functionality and SDK
    • OTLP Exporters: Direct export to Last9 for traces and metrics
    • Gin Instrumentation: Automatic HTTP request/response tracing
    • Database/Redis: SQL database and Redis operation tracing
    • HTTP Client: External API call instrumentation
  2. Set Environment Variables

    Configure OpenTelemetry environment variables for your Gin application:

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

    Replace gin-api-server with your actual service name.

  3. Create Instrumentation Setup

    Create a comprehensive instrumentation configuration file instrumentation.go:

    package main
    import (
    "context"
    "fmt"
    "log"
    "os"
    "time"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
    "go.opentelemetry.io/otel/propagation"
    "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 *metric.MeterProvider
    Tracer trace.Tracer
    Resource *resource.Resource
    }
    func NewInstrumentation() (*Instrumentation, error) {
    // Create resource with service information
    res, err := createResource()
    if err != nil {
    return nil, fmt.Errorf("failed to create resource: %w", err)
    }
    // Initialize tracer provider
    tp, err := initTracerProvider(res)
    if err != nil {
    return nil, fmt.Errorf("failed to create tracer provider: %w", err)
    }
    // Initialize meter provider
    mp, err := initMeterProvider(res)
    if err != nil {
    return nil, fmt.Errorf("failed to create meter provider: %w", err)
    }
    // Set global providers
    otel.SetTracerProvider(tp)
    otel.SetMeterProvider(mp)
    otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
    propagation.TraceContext{},
    propagation.Baggage{},
    ))
    return &Instrumentation{
    TracerProvider: tp,
    MeterProvider: mp,
    Tracer: tp.Tracer(getServiceName()),
    Resource: res,
    }, nil
    }
    func createResource() (*resource.Resource, error) {
    return resource.New(context.Background(),
    resource.WithFromEnv(),
    resource.WithTelemetrySDK(),
    resource.WithProcess(),
    resource.WithOS(),
    resource.WithContainer(),
    resource.WithHost(),
    resource.WithAttributes(
    semconv.ServiceNameKey.String(getServiceName()),
    semconv.ServiceVersionKey.String(getServiceVersion()),
    semconv.DeploymentEnvironmentKey.String(getEnvironment()),
    ),
    )
    }
    func initTracerProvider(res *resource.Resource) (*sdktrace.TracerProvider, error) {
    // Create OTLP trace exporter
    exporter, err := otlptracehttp.New(context.Background())
    if err != nil {
    return nil, fmt.Errorf("failed to create trace exporter: %w", err)
    }
    // Create tracer provider with batch processor
    tp := sdktrace.NewTracerProvider(
    sdktrace.WithBatcher(exporter,
    sdktrace.WithBatchTimeout(5*time.Second),
    sdktrace.WithMaxExportBatchSize(512),
    ),
    sdktrace.WithResource(res),
    sdktrace.WithSampler(sdktrace.AlwaysSample()),
    )
    return tp, nil
    }
    func initMeterProvider(res *resource.Resource) (*metric.MeterProvider, error) {
    // Create OTLP metric exporter
    exporter, err := otlpmetricgrpc.New(context.Background())
    if err != nil {
    return nil, fmt.Errorf("failed to create metric exporter: %w", err)
    }
    // Create meter provider with periodic reader
    mp := metric.NewMeterProvider(
    metric.WithResource(res),
    metric.WithReader(metric.NewPeriodicReader(exporter,
    metric.WithInterval(30*time.Second),
    )),
    )
    return mp, nil
    }
    func (i *Instrumentation) Shutdown() error {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    // Shutdown tracer provider
    if err := i.TracerProvider.Shutdown(ctx); err != nil {
    log.Printf("Error shutting down tracer provider: %v", err)
    }
    // Shutdown meter provider
    if err := i.MeterProvider.Shutdown(ctx); err != nil {
    log.Printf("Error shutting down meter provider: %v", err)
    }
    return nil
    }
    // Helper functions to get configuration from environment
    func getServiceName() string {
    if name := os.Getenv("OTEL_SERVICE_NAME"); name != "" {
    return name
    }
    return "gin-application"
    }
    func getServiceVersion() string {
    if version := os.Getenv("SERVICE_VERSION"); version != "" {
    return version
    }
    return "1.0.0"
    }
    func getEnvironment() string {
    if env := os.Getenv("DEPLOYMENT_ENV"); env != "" {
    return env
    }
    return "production"
    }
  4. Instrument Your Gin Application

    Update your main application file to include OpenTelemetry instrumentation:

    package main
    import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
    "github.com/gin-gonic/gin"
    "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
    )
    func main() {
    // Initialize OpenTelemetry instrumentation
    instr, err := NewInstrumentation()
    if err != nil {
    log.Fatalf("Failed to initialize instrumentation: %v", err)
    }
    // Ensure proper shutdown
    defer func() {
    if err := instr.Shutdown(); err != nil {
    log.Printf("Error during shutdown: %v", err)
    }
    }()
    // Create Gin router with OpenTelemetry middleware
    r := gin.Default()
    // Add OpenTelemetry middleware
    r.Use(otelgin.Middleware(getServiceName()))
    // Define routes
    r.GET("/health", healthHandler)
    r.GET("/", indexHandler)
    r.GET("/users/:id", getUserHandler)
    r.POST("/users", createUserHandler)
    // Start HTTP server
    srv := &http.Server{
    Addr: ":8080",
    Handler: r,
    }
    // Graceful shutdown
    go func() {
    if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
    log.Fatalf("Server failed to start: %v", err)
    }
    }()
    log.Println("Server started on :8080")
    // Wait for interrupt signal
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("Shutting down server...")
    // Shutdown server
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    if err := srv.Shutdown(ctx); err != nil {
    log.Fatal("Server forced to shutdown:", err)
    }
    log.Println("Server exited")
    }
    func healthHandler(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"status": "healthy"})
    }
    func indexHandler(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"message": "Welcome to Gin API"})
    }
    func getUserHandler(c *gin.Context) {
    userID := c.Param("id")
    // Add custom span attributes
    span := trace.SpanFromContext(c.Request.Context())
    span.SetAttributes(
    attribute.String("user.id", userID),
    attribute.String("operation", "get_user"),
    )
    // Simulate user lookup
    user := gin.H{
    "id": userID,
    "name": "John Doe",
    "email": "john@example.com",
    }
    c.JSON(http.StatusOK, user)
    }
    func createUserHandler(c *gin.Context) {
    // Add custom span for user creation
    span := trace.SpanFromContext(c.Request.Context())
    span.SetAttributes(
    attribute.String("operation", "create_user"),
    )
    c.JSON(http.StatusCreated, gin.H{"message": "User created"})
    }
  5. Add External API Call Instrumentation

    Instrument outbound HTTP calls to external services:

    package main
    import (
    "context"
    "encoding/json"
    "net/http"
    "github.com/gin-gonic/gin"
    "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/trace"
    )
    // Create instrumented HTTP client
    var httpClient = &http.Client{
    Transport: otelhttp.NewTransport(http.DefaultTransport),
    }
    func externalAPIHandler(c *gin.Context) {
    span := trace.SpanFromContext(c.Request.Context())
    span.SetAttributes(attribute.String("operation", "external_api_call"))
    // Make external API call with tracing
    req, err := http.NewRequestWithContext(
    c.Request.Context(),
    "GET",
    "https://api.external-service.com/users",
    nil,
    )
    if err != nil {
    span.SetAttributes(attribute.String("error", err.Error()))
    c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create request"})
    return
    }
    // Add custom headers
    req.Header.Set("User-Agent", "gin-service/1.0")
    resp, err := httpClient.Do(req)
    if err != nil {
    span.SetAttributes(attribute.String("error", err.Error()))
    c.JSON(http.StatusInternalServerError, gin.H{"error": "External API error"})
    return
    }
    defer resp.Body.Close()
    span.SetAttributes(
    attribute.Int("http.status_code", resp.StatusCode),
    attribute.String("http.url", req.URL.String()),
    )
    if resp.StatusCode != http.StatusOK {
    c.JSON(resp.StatusCode, gin.H{"error": "External API returned error"})
    return
    }
    var result interface{}
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
    span.SetAttributes(attribute.String("error", err.Error()))
    c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to decode response"})
    return
    }
    c.JSON(http.StatusOK, result)
    }
  6. Run Your Instrumented Application

    Build and run your Gin application with OpenTelemetry instrumentation:

    # Build the application
    go build -o gin-app
    # Run with environment variables
    ./gin-app
    # Or run directly with go
    go run *.go

Understanding Gin Instrumentation

The OpenTelemetry Gin integration provides comprehensive observability:

HTTP Request Tracing

  • Request/Response Cycles: Complete HTTP request lifecycle tracing
  • Route Information: Gin route patterns and handler identification
  • Request Metadata: HTTP method, status codes, response times
  • Error Tracking: Automatic error detection and span status updates

Middleware Integration

  • Automatic Instrumentation: Zero-code instrumentation via Gin middleware
  • Context Propagation: Seamless trace context passing through request pipeline
  • Custom Attributes: Add business-specific metadata to spans
  • Performance Metrics: Request duration, throughput, and error rates

Database and External Services

  • SQL Database Tracing: Query execution, connection pooling, transaction tracking
  • Redis Operations: Cache operations with hit/miss ratios and performance metrics
  • HTTP Client Calls: External API calls with request/response details
  • Error Handling: Comprehensive error tracking across all integrations

Advanced Configuration

Custom Middleware and Spans

Add custom spans for business logic:

func businessLogicMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
tracer := otel.Tracer("business-logic")
ctx, span := tracer.Start(c.Request.Context(), "business.operation")
defer span.End()
// Add custom attributes
span.SetAttributes(
attribute.String("business.operation", "user_validation"),
attribute.String("tenant.id", getTenantID(c)),
)
// Update context
c.Request = c.Request.WithContext(ctx)
c.Next()
}
}

Custom Metrics

Create application-specific metrics:

import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric"
)
var (
requestCounter metric.Int64Counter
requestDuration metric.Float64Histogram
)
func initCustomMetrics() {
meter := otel.Meter("gin-application")
var err error
requestCounter, err = meter.Int64Counter(
"http_requests_total",
metric.WithDescription("Total HTTP requests"),
)
if err != nil {
log.Fatal("Failed to create counter:", err)
}
requestDuration, err = meter.Float64Histogram(
"http_request_duration_seconds",
metric.WithDescription("HTTP request duration"),
metric.WithUnit("s"),
)
if err != nil {
log.Fatal("Failed to create histogram:", err)
}
}
func metricsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
duration := time.Since(start).Seconds()
// Record metrics
requestCounter.Add(c.Request.Context(), 1,
metric.WithAttributes(
attribute.String("method", c.Request.Method),
attribute.String("route", c.FullPath()),
attribute.Int("status", c.Writer.Status()),
),
)
requestDuration.Record(c.Request.Context(), duration,
metric.WithAttributes(
attribute.String("method", c.Request.Method),
attribute.String("route", c.FullPath()),
),
)
}
}

Error Handling and Span Status

Proper error handling with OpenTelemetry:

import (
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)
func errorHandlingExample(c *gin.Context) {
span := trace.SpanFromContext(c.Request.Context())
// Simulate business logic
if err := performBusinessOperation(); err != nil {
// Record error in span
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
span.SetAttributes(
attribute.String("error.type", "business_logic_error"),
attribute.String("error.detail", err.Error()),
)
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal server error",
"trace_id": span.SpanContext().TraceID().String(),
})
return
}
// Success case
span.SetStatus(codes.Ok, "Operation completed successfully")
c.JSON(http.StatusOK, gin.H{"status": "success"})
}

Verification

  1. Check Application Startup

    Verify OpenTelemetry initializes correctly:

    # Look for initialization logs
    go run *.go
    # Should see logs like:
    # "OpenTelemetry instrumentation initialized"
    # "Server started on :8080"
  2. Generate Test Traffic

    Make requests to your instrumented endpoints:

    # Health check
    curl http://localhost:8080/health
    # API endpoints
    curl http://localhost:8080/users/123
    curl -X POST http://localhost:8080/users \
    -H "Content-Type: application/json" \
    -d '{"name":"John","email":"john@example.com"}'
    # Cache operations (if implemented)
    curl -X POST http://localhost:8080/cache/test-key \
    -H "Content-Type: application/json" \
    -d '{"value":"test-value","ttl":3600}'
    curl http://localhost:8080/cache/test-key
  3. Test Database Operations

    If using database integration, perform database operations to generate database spans.

  4. Verify Traces in Last9

    Log into your Last9 account and check that traces are being received in the Traces dashboard.

    Look for:

    • HTTP request traces with Gin route information
    • Database query spans (if database integration is enabled)
    • Redis operation spans (if Redis integration is enabled)
    • External API call traces (if external service calls are made)
    • Custom business logic spans

Best Practices

Development

  • Environment Variables: Use environment variables for configuration
  • Service Naming: Use descriptive, consistent service names across environments
  • Resource Attribution: Include version, environment, and deployment metadata
  • Error Handling: Always set span status for errors and include relevant error details

Production

  • Sampling: Configure appropriate sampling rates for production workloads
  • Resource Limits: Set memory and CPU limits for telemetry processing
  • Graceful Shutdown: Ensure proper cleanup of OpenTelemetry resources
  • Health Checks: Include telemetry health in your application health checks

Monitoring Strategy

  • Custom Spans: Add spans for critical business operations
  • Metrics: Combine tracing with custom metrics for comprehensive observability
  • Alerting: Set up alerts on trace data for error rates and latency
  • Context Propagation: Ensure trace context flows through all service boundaries

Security

  • Credential Management: Store sensitive configuration in environment variables
  • Data Privacy: Be mindful of sensitive data in trace attributes
  • Network Security: Ensure secure communication to Last9 endpoints

Supported Gin Features

The OpenTelemetry Gin integration works seamlessly with:

  • Middleware Chain: Integrates with existing Gin middleware
  • Route Groups: Supports nested route groups and common middleware
  • Custom Recovery: Works with custom recovery middleware
  • Static File Serving: Traces static file requests
  • WebSocket Connections: Basic WebSocket request tracing
  • File Uploads: Traces multipart file upload requests

Need Help?

If you encounter any issues or have questions: