tracing

package
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 27, 2025 License: MIT Imports: 10 Imported by: 0

README

tracing Package

Distributed tracing with OpenTelemetry and OTLP for request tracking across microservices.

Features

  • Distributed Tracing: Track requests across services
  • OpenTelemetry: Industry-standard tracing
  • OTLP Export: Compatible with Jaeger, Zipkin, etc.
  • Automatic Context Propagation: Span context in requests
  • Custom Attributes: Add business context to spans
  • Sampling: Control trace volume
  • Error Recording: Automatic error tracking

Installation

import "github.com/LaRestoOU/laresto-go-common/pkg/tracing"

Quick Start

Initialize Tracing
cfg := tracing.Config{
    ServiceName:    "auth-service",
    ServiceVersion: "1.0.0",
    Environment:    "production",
    OTLPEndpoint:   "localhost:4318",
    Enabled:        true,
    SampleRate:     1.0,      // Trace all requests
    Insecure:       false,    // Use TLS in production
}

log := logger.New(logger.Config{...})
provider, err := tracing.NewProvider(cfg, log)
if err != nil {
    log.Fatal("Failed to initialize tracing", err)
}
defer provider.Shutdown(context.Background())
Create Spans
func ProcessOrder(ctx context.Context, orderID string) error {
    // Start span
    ctx, span := tracing.StartSpan(ctx, "auth-service", "process-order")
    defer span.End()
    
    // Add attributes
    tracing.SetAttributes(ctx,
        tracing.OrderID.String(orderID),
        tracing.UserID.String("user-123"),
    )
    
    // Process order
    if err := validateOrder(ctx, orderID); err != nil {
        tracing.RecordError(ctx, err)
        return err
    }
    
    // Add event
    tracing.AddEvent(ctx, "order-validated",
        attribute.String("status", "valid"),
    )
    
    return nil
}

Configuration

type Config struct {
    // ServiceName is the service identifier
    ServiceName string // "auth-service"
    
    // ServiceVersion is the service version
    ServiceVersion string // "1.0.0"
    
    // Environment is deployment env
    Environment string // "production"
    
    // OTLPEndpoint is the collector endpoint
    // Jaeger OTLP HTTP: "localhost:4318"
    // Jaeger OTLP gRPC: "localhost:4317"
    OTLPEndpoint string
    
    // Enabled toggles tracing
    Enabled bool
    
    // SampleRate (0.0 to 1.0)
    // 1.0 = trace all, 0.1 = trace 10%
    SampleRate float64
    
    // Insecure for development (no TLS)
    Insecure bool
}

Jaeger Setup (Week 2 Infrastructure)

Your Docker Compose already has Jaeger with OTLP support:

# Week 2: laresto-docker-compose
jaeger:
  ports:
    - "16686:16686"  # UI
    - "4317:4317"    # OTLP gRPC
    - "4318:4318"    # OTLP HTTP

Access Jaeger UI: http://localhost:16686

Usage Patterns

HTTP Request Tracing
func (h *Handler) GetUser(c *gin.Context) {
    // Start span for HTTP request
    ctx, span := tracing.StartSpan(c.Request.Context(), "auth-service", "GET /users/:id")
    defer span.End()
    
    // Add HTTP attributes
    tracing.SetAttributes(ctx,
        tracing.HTTPMethod.String(c.Request.Method),
        tracing.HTTPRoute.String("/users/:id"),
        tracing.HTTPURL.String(c.Request.URL.String()),
    )
    
    // Get user
    userID := c.Param("id")
    user, err := h.service.GetUser(ctx, userID)
    if err != nil {
        tracing.RecordError(ctx, err)
        tracing.SetAttributes(ctx, tracing.HTTPStatusCode.Int(500))
        c.JSON(500, gin.H{"error": "Failed to get user"})
        return
    }
    
    tracing.SetAttributes(ctx, tracing.HTTPStatusCode.Int(200))
    c.JSON(200, user)
}
Database Query Tracing
func (r *UserRepository) FindByID(ctx context.Context, id string) (*User, error) {
    // Start database span
    ctx, span := tracing.StartSpan(ctx, "auth-service", "db.FindUser")
    defer span.End()
    
    // Add database attributes
    tracing.SetAttributes(ctx,
        tracing.DBSystem.String("postgresql"),
        tracing.DBName.String("customer_db"),
        tracing.DBOperation.String("SELECT"),
        tracing.DBStatement.String("SELECT * FROM users WHERE id = $1"),
    )
    
    var user User
    err := r.db.GetContext(ctx, &user, "SELECT * FROM users WHERE id = $1", id)
    if err != nil {
        tracing.RecordError(ctx, err)
        return nil, err
    }
    
    return &user, nil
}
Service-to-Service Calls
func (c *UserServiceClient) GetUser(ctx context.Context, userID string) (*User, error) {
    // Start span for external call
    ctx, span := tracing.StartSpan(ctx, "order-service", "http.GetUser")
    defer span.End()
    
    url := fmt.Sprintf("%s/users/%s", c.baseURL, userID)
    
    tracing.SetAttributes(ctx,
        tracing.HTTPMethod.String("GET"),
        tracing.HTTPURL.String(url),
    )
    
    // HTTP client automatically propagates span context
    resp, err := c.httpClient.Get(ctx, url, nil)
    if err != nil {
        tracing.RecordError(ctx, err)
        return nil, err
    }
    
    tracing.SetAttributes(ctx, tracing.HTTPStatusCode.Int(resp.StatusCode))
    
    var user User
    resp.JSON(&user)
    return &user, nil
}
Kafka Event Tracing
func (p *EventPublisher) PublishUserRegistered(ctx context.Context, event UserRegisteredEvent) error {
    // Start span
    ctx, span := tracing.StartSpan(ctx, "auth-service", "kafka.PublishUserRegistered")
    defer span.End()
    
    tracing.SetAttributes(ctx,
        attribute.String("messaging.system", "kafka"),
        attribute.String("messaging.destination", "user.registered"),
        tracing.UserID.String(event.UserID),
    )
    
    err := p.producer.Publish(ctx, "user.registered", event.UserID, event)
    if err != nil {
        tracing.RecordError(ctx, err)
        return err
    }
    
    return nil
}

Predefined Attributes

// HTTP
tracing.HTTPMethod.String("GET")
tracing.HTTPStatusCode.Int(200)
tracing.HTTPRoute.String("/users/:id")
tracing.HTTPURL.String("https://api.laresto.com/users/123")

// Database
tracing.DBSystem.String("postgresql")
tracing.DBName.String("customer_db")
tracing.DBStatement.String("SELECT * FROM users")
tracing.DBOperation.String("SELECT")

// User
tracing.UserID.String("user-123")
tracing.UserEmail.String("user@example.com")
tracing.UserRole.String("admin")

// Business
tracing.OrderID.String("order-456")
tracing.VenueID.String("venue-789")
tracing.PaymentID.String("payment-012")

// Error
tracing.ErrorType.String("ValidationError")
tracing.ErrorMessage.String("Invalid email format")

Span Events

Add events to mark important moments:

ctx, span := tracing.StartSpan(ctx, "payment-service", "process-payment")
defer span.End()

tracing.AddEvent(ctx, "payment-validated",
    attribute.String("method", "credit_card"),
)

// Process payment...

tracing.AddEvent(ctx, "payment-authorized",
    attribute.String("authorization_code", "ABC123"),
)

tracing.AddEvent(ctx, "payment-completed",
    attribute.Float64("amount", 99.99),
)

Error Recording

ctx, span := tracing.StartSpan(ctx, "order-service", "create-order")
defer span.End()

order, err := service.CreateOrder(ctx, req)
if err != nil {
    // Record error in span
    tracing.RecordError(ctx, err)
    
    // Add error details
    tracing.SetAttributes(ctx,
        tracing.ErrorType.String("ValidationError"),
        tracing.ErrorMessage.String(err.Error()),
    )
    
    return nil, err
}

Sampling

Control trace volume:

// Production: Sample 10% of requests
cfg := tracing.Config{
    SampleRate: 0.1,  // 10% of requests
}

// Development: Trace everything
cfg := tracing.Config{
    SampleRate: 1.0,  // 100% of requests
}

// High-traffic: Sample 1%
cfg := tracing.Config{
    SampleRate: 0.01,  // 1% of requests
}

Distributed Trace Example

User Request → API Gateway → Auth Service → Database
                            ↓
                         User Service → Database
                            ↓
                         Kafka → Email Service

Trace spans:

Trace ID: abc-123-def-456

├─ API Gateway [50ms]
│  └─ Auth Service [40ms]
│     ├─ DB: Check credentials [10ms]
│     └─ User Service [25ms]
│        └─ DB: Get user profile [5ms]
│     └─ Kafka: user.registered [5ms]
└─ Email Service [100ms]
   └─ Send welcome email [95ms]

View in Jaeger UI: http://localhost:16686

Best Practices

DO ✅
// Start spans for significant operations
ctx, span := tracing.StartSpan(ctx, "service-name", "operation-name")
defer span.End()

// Add meaningful attributes
tracing.SetAttributes(ctx,
    tracing.UserID.String(userID),
    tracing.OrderID.String(orderID),
)

// Record errors
if err != nil {
    tracing.RecordError(ctx, err)
}

// Use consistent span names
"http.GET /users/:id"
"db.FindUser"
"kafka.PublishEvent"

// Sample in production
SampleRate: 0.1  // 10% is usually enough
DON'T ❌
// Don't forget to end spans
ctx, span := tracing.StartSpan(ctx, "service", "op")
// Missing: defer span.End()

// Don't add sensitive data
tracing.SetAttributes(ctx,
    attribute.String("password", password), // BAD!
    attribute.String("credit_card", card),  // BAD!
)

// Don't create too many spans
for item := range items {
    ctx, span := tracing.StartSpan(ctx, "service", "process-item")
    // Creates thousands of spans!
}

// Don't trace everything in production
SampleRate: 1.0  // Too much data in prod!

Testing

func TestTracing(t *testing.T) {
    cfg := tracing.Config{
        ServiceName: "test-service",
        Enabled:     false,  // Disable in tests
    }
    
    provider, _ := tracing.NewProvider(cfg, logger.NewDefault())
    defer provider.Shutdown(context.Background())
    
    // Create spans (noop when disabled)
    ctx, span := tracing.StartSpan(context.Background(), "test", "operation")
    defer span.End()
    
    // Still safe to call
    tracing.SetAttributes(ctx, attribute.String("test", "value"))
}

Jaeger UI

Access: http://localhost:16686

Search traces:

  • Service: auth-service
  • Operation: GET /users/:id
  • Tags: user.id=123
  • Duration: > 100ms

View details:

  • Full trace timeline
  • All spans with duration
  • Attributes and events
  • Error information

Performance

Overhead:

  • Enabled, not sampled: < 1µs per span
  • Enabled, sampled: ~10-100µs per span
  • Disabled: ~0µs (noop)

Best practices:

  • Sample 10-20% in production
  • Create spans for significant operations only
  • Batch export to collector (automatic)

License

MIT License - see LICENSE file for details

Documentation

Overview

Package tracing provides distributed tracing with OpenTelemetry.

Index

Constants

This section is empty.

Variables

View Source
var (
	// HTTP attributes
	HTTPMethod     = attribute.Key("http.method")
	HTTPStatusCode = attribute.Key("http.status_code")
	HTTPRoute      = attribute.Key("http.route")
	HTTPURL        = attribute.Key("http.url")

	// Database attributes
	DBSystem    = attribute.Key("db.system")
	DBName      = attribute.Key("db.name")
	DBStatement = attribute.Key("db.statement")
	DBOperation = attribute.Key("db.operation")

	// User attributes
	UserID    = attribute.Key("user.id")
	UserEmail = attribute.Key("user.email")
	UserRole  = attribute.Key("user.role")

	// Business attributes
	OrderID   = attribute.Key("order.id")
	VenueID   = attribute.Key("venue.id")
	PaymentID = attribute.Key("payment.id")

	// Error attributes
	ErrorType    = attribute.Key("error.type")
	ErrorMessage = attribute.Key("error.message")
)

Common attribute keys for consistency across services.

Functions

func AddEvent

func AddEvent(ctx context.Context, name string, attrs ...attribute.KeyValue)

AddEvent adds an event to the current span.

func RecordError

func RecordError(ctx context.Context, err error)

RecordError records an error on the current span.

func SetAttributes

func SetAttributes(ctx context.Context, attrs ...attribute.KeyValue)

SetAttributes sets attributes on the current span.

func StartSpan

func StartSpan(ctx context.Context, tracerName, spanName string) (context.Context, trace.Span)

StartSpan starts a new span.

Types

type Config

type Config struct {
	// ServiceName is the name of the service
	ServiceName string

	// ServiceVersion is the version of the service
	ServiceVersion string

	// Environment is the deployment environment (dev, staging, prod)
	Environment string

	// OTLPEndpoint is the OTLP collector endpoint
	// Example: "localhost:4318" (Jaeger OTLP HTTP endpoint)
	OTLPEndpoint string

	// Enabled determines if tracing is enabled
	Enabled bool

	// SampleRate is the sampling rate (0.0 to 1.0)
	// 1.0 = trace all requests, 0.1 = trace 10% of requests
	SampleRate float64

	// Insecure determines if connection should be insecure (for development)
	Insecure bool
}

Config holds tracing configuration.

type Provider

type Provider struct {
	// contains filtered or unexported fields
}

Provider manages the tracing provider.

func NewProvider

func NewProvider(cfg Config, log *logger.Logger) (*Provider, error)

NewProvider creates a new tracing provider.

func (*Provider) Shutdown

func (p *Provider) Shutdown(ctx context.Context) error

Shutdown shuts down the tracing provider.

func (*Provider) Tracer

func (p *Provider) Tracer(name string) trace.Tracer

Tracer returns a tracer for the given name.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL