tracesfx

package
v0.7.2 Latest Latest
Warning

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

Go to latest
Published: Jun 12, 2025 License: Apache-2.0 Imports: 15 Imported by: 0

README ΒΆ

πŸ” tracesfx - Distributed Tracing with OpenTelemetry

tracesfx provides a clean, lightweight wrapper around OpenTelemetry tracing that integrates seamlessly with logfx and metricsfx to create a complete observability stack.

✨ Key Features

  • πŸš€ Zero-Config Start - Works out of the box with sensible defaults
  • πŸ”— Seamless Integration - Native correlation with logs and metrics
  • πŸ“Š OTLP Support - Built-in OpenTelemetry Protocol export
  • 🎯 Clean API - Simple, focused interface without unnecessary complexity
  • πŸ”§ Configurable Sampling - Control trace volume with ratio-based sampling
  • πŸ“ Auto-Correlation - Automatic correlation ID propagation from logfx

πŸ—οΈ Architecture Integration

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   logfx     │◄──►│  tracesfx   │◄──►│ metricsfx   β”‚
β”‚ (Logging)   β”‚    β”‚ (Tracing)   β”‚    β”‚ (Metrics)   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚                   β”‚                   β”‚
       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚  OpenTelemetry      β”‚
               β”‚  Collector/Backend  β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“¦ Installation

go get go.opentelemetry.io/otel

The required OpenTelemetry dependencies are automatically included.

πŸš€ Quick Start

Basic Setup
package main

import (
    "context"
    "log"

    "github.com/eser/ajan/tracesfx"
)

func main() {
    // Create and initialize traces provider
    provider := tracesfx.NewTracesProvider(&tracesfx.Config{
        ServiceName:    "my-service",
        ServiceVersion: "1.0.0",
        OTLPEndpoint:   "http://localhost:4318",
        OTLPInsecure:   true,
        SampleRatio:    1.0, // Sample 100% for development
    })

    if err := provider.Init(); err != nil {
        log.Fatal(err)
    }
    defer provider.Shutdown(context.Background())

    // Get a tracer
    tracer := provider.Tracer("my-component")

    // Create spans
    ctx, span := tracer.Start(context.Background(), "do-work")
    defer span.End()

    // Your business logic here
    doWork(ctx)
}

func doWork(ctx context.Context) {
    // Spans automatically propagate through context
    span := trace.SpanFromContext(ctx)
    span.SetAttributes(attribute.String("work.type", "important"))
}
Integration with logfx
import (
    "github.com/eser/ajan/logfx"
    "github.com/eser/ajan/tracesfx"
)

func setupObservability() {
    // Setup tracing
    tracesProvider := tracesfx.NewTracesProvider(&tracesfx.Config{
        ServiceName:  "my-service",
        OTLPEndpoint: "http://localhost:4318",
    })
    tracesProvider.Init()

    // Setup logging
    logger := logfx.NewLogger(
        logfx.WithOTLP("http://localhost:4318", false),
    )

    // Use together - trace IDs automatically appear in logs
    tracer := tracesProvider.Tracer("my-service")
    ctx, span := tracer.Start(context.Background(), "user-request")

    // This log will include trace_id and span_id
    logger.InfoContext(ctx, "Processing user request",
        slog.String("user_id", "12345"))

    span.End()
}
Correlation ID Integration
func handleRequest(w http.ResponseWriter, r *http.Request) {
    // Extract or generate correlation ID (typically done by middleware)
    correlationID := r.Header.Get("X-Correlation-ID")
    if correlationID == "" {
        correlationID = generateCorrelationID()
    }

    // Add to context
    ctx := tracesfx.SetCorrelationIDInContext(r.Context(), correlationID)

    // Start span with automatic correlation
    tracer := getTracer()
    ctx, span := tracer.StartSpanWithCorrelation(ctx, "handle-request")
    defer span.End()

    // Both traces and logs will include correlation_id
    processRequest(ctx)
}

βš™οΈ Configuration

Config Structure
type Config struct {
    // Service identification
    ServiceName    string        `conf:"service_name"`
    ServiceVersion string        `conf:"service_version"`

    // OpenTelemetry Collector
    OTLPEndpoint   string        `conf:"otlp_endpoint"`     // e.g. "http://localhost:4318"
    OTLPInsecure   bool          `conf:"otlp_insecure"`     // default: true

    // Sampling
    SampleRatio    float64       `conf:"sample_ratio"`      // 0.0 to 1.0, default: 1.0

    // Batching
    BatchTimeout   time.Duration `conf:"batch_timeout"`     // default: 5s
    BatchSize      int           `conf:"batch_size"`        // default: 512
}
Environment Variables
# Service information
SERVICE_NAME=my-service
SERVICE_VERSION=1.0.0

# OTLP configuration
OTLP_ENDPOINT=http://localhost:4318
OTLP_INSECURE=true

# Sampling configuration
SAMPLE_RATIO=0.1  # Sample 10% in production

# Batch configuration
BATCH_TIMEOUT=5s
BATCH_SIZE=512

πŸ”— Integration Patterns

Complete Observability Stack
type ObservabilityStack struct {
    Traces  *tracesfx.TracesProvider
    Metrics *metricsfx.MetricsProvider
    Logger  *logfx.Logger
}

func NewObservabilityStack(config *Config) *ObservabilityStack {
    // Shared OTLP endpoint for all signals
    otlpEndpoint := config.OTLPEndpoint

    return &ObservabilityStack{
        Traces: tracesfx.NewTracesProvider(&tracesfx.Config{
            ServiceName:  config.ServiceName,
            OTLPEndpoint: otlpEndpoint,
            SampleRatio:  config.TraceSampleRatio,
        }),
        Metrics: metricsfx.NewMetricsProvider(&metricsfx.Config{
            ServiceName:  config.ServiceName,
            OTLPEndpoint: otlpEndpoint,
        }),
        Logger: logfx.NewLogger(
            logfx.WithWriter(os.Stdout),
            logfx.WithConfig(&logfx.Config{
                Level:        config.LogLevel,
                OTLPEndpoint: otlpEndpoint,
            }),
        ),
    }
}

func (o *ObservabilityStack) Init() error {
    if err := o.Traces.Init(); err != nil {
        return err
    }
    if err := o.Metrics.Init(); err != nil {
        return err
    }
    return nil
}
HTTP Middleware Integration
func TracingMiddleware(tracer *tracesfx.Tracer) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ctx, span := tracer.Start(r.Context(), fmt.Sprintf("%s %s", r.Method, r.URL.Path))
            defer span.End()

            // Add HTTP attributes
            span.SetAttributes(
                attribute.String("http.method", r.Method),
                attribute.String("http.url", r.URL.String()),
                attribute.String("http.user_agent", r.UserAgent()),
            )

            // Continue with tracing context
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

πŸ“Š Span Management

Creating Spans
// Basic span
ctx, span := tracer.Start(ctx, "operation-name")
defer span.End()

// With attributes
ctx, span := tracer.Start(ctx, "database-query",
    trace.WithAttributes(
        attribute.String("db.table", "users"),
        attribute.String("db.operation", "SELECT"),
    ))
defer span.End()

// With correlation (automatic)
ctx, span := tracer.StartSpanWithCorrelation(ctx, "api-call")
defer span.End()
Span Operations
// Set attributes
span.SetAttributes(
    attribute.String("user.id", "12345"),
    attribute.Int("batch.size", 100),
)

// Add events
span.AddEvent("cache-miss")
span.AddEvent("retry-attempt",
    attribute.Int("attempt", 2))

// Record errors
if err != nil {
    span.RecordError(err)
    span.SetStatus(codes.Error, "operation failed")
}

// Success
span.SetStatus(codes.Ok, "completed successfully")

πŸ”„ Context Propagation

Automatic Propagation
func parentOperation(ctx context.Context) {
    tracer := getTracer()
    ctx, span := tracer.Start(ctx, "parent")
    defer span.End()

    // Child operations automatically inherit trace context
    childOperation(ctx)  // Will be a child span
}

func childOperation(ctx context.Context) {
    tracer := getTracer()
    ctx, span := tracer.Start(ctx, "child")
    defer span.End()

    // This span will be a child of "parent"
}
Cross-Service Propagation
func makeHTTPRequest(ctx context.Context, url string) {
    req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)

    // OpenTelemetry automatically injects trace headers
    client := &http.Client{}
    resp, err := client.Do(req)
    // Trace context is propagated to the remote service
}

🎯 Best Practices

1. Span Naming
// βœ… Good - descriptive, hierarchical
tracer.Start(ctx, "user.authentication.validate")
tracer.Start(ctx, "database.users.query")
tracer.Start(ctx, "cache.redis.get")

// ❌ Avoid - too generic
tracer.Start(ctx, "process")
tracer.Start(ctx, "operation")
2. Attribute Management
// βœ… Good - semantic conventions
span.SetAttributes(
    semconv.HTTPMethodKey.String("GET"),
    semconv.HTTPURLKey.String(url),
    semconv.HTTPStatusCodeKey.Int(200),
)

// βœ… Good - business context
span.SetAttributes(
    attribute.String("user.role", "admin"),
    attribute.String("tenant.id", "org-123"),
)
3. Error Handling
func operationWithTracing(ctx context.Context) error {
    ctx, span := tracer.Start(ctx, "risky-operation")
    defer span.End()

    result, err := riskyOperation()
    if err != nil {
        span.RecordError(err)
        span.SetStatus(codes.Error, "operation failed")
        return err
    }

    span.SetStatus(codes.Ok, "success")
    span.SetAttributes(attribute.String("result", result))
    return nil
}
4. Sampling Configuration
// Development - trace everything
config.SampleRatio = 1.0

// Production - sample based on load
config.SampleRatio = 0.1  // 10%

// High-volume services
config.SampleRatio = 0.01 // 1%

πŸ”§ Advanced Usage

Custom Attributes
// Business-specific attributes
span.SetAttributes(
    attribute.String("order.id", orderID),
    attribute.String("customer.tier", "premium"),
    attribute.Int("item.count", len(items)),
    attribute.Float64("order.total", 299.99),
)
// Link to related operations
ctx, span := tracer.Start(ctx, "batch-process",
    trace.WithLinks(trace.Link{
        SpanContext: relatedSpanContext,
    }))
Manual Instrumentation
func instrumentedFunction(ctx context.Context) {
    tracer := otel.Tracer("my-component")
    ctx, span := tracer.Start(ctx, "custom-operation")
    defer span.End()

    // Add custom logic
    span.AddEvent("starting-phase-1")
    phase1(ctx)

    span.AddEvent("starting-phase-2")
    phase2(ctx)
}

πŸ› Debugging

Trace Verification
// Check if tracing is active
if span := trace.SpanFromContext(ctx); span.IsRecording() {
    // Tracing is active
    traceID := span.SpanContext().TraceID().String()
    fmt.Printf("Trace ID: %s\n", traceID)
}

// Get trace/span IDs for logging
traceID := tracesfx.GetTraceIDFromContext(ctx)
spanID := tracesfx.GetSpanIDFromContext(ctx)
No-Op Mode
// When OTLPEndpoint is empty, tracesfx uses no-op tracers
config := &tracesfx.Config{
    ServiceName:  "my-service",
    OTLPEndpoint: "", // No tracing overhead
}

πŸ“ˆ Performance Considerations

  • Sampling: Use appropriate sample ratios for production workloads
  • Batch Configuration: Tune batch size and timeout for your throughput
  • Attribute Limits: Be mindful of attribute cardinality
  • Context Overhead: Minimal overhead when properly configured

πŸ”— Integration Examples

With HTTP Server
func main() {
    provider := tracesfx.NewTracesProvider(&tracesfx.Config{
        ServiceName:  "web-api",
        OTLPEndpoint: "http://localhost:4318",
    })
    provider.Init()
    defer provider.Shutdown(context.Background())

    tracer := provider.Tracer("http-server")

    http.HandleFunc("/users", TracingMiddleware(tracer)(UsersHandler))
    http.ListenAndServe(":8080", nil)
}
With gRPC
import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"

func setupGRPCServer() {
    provider := tracesfx.NewTracesProvider(config)
    provider.Init()

    server := grpc.NewServer(
        grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()),
        grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor()),
    )
}
  • logfx - Structured logging with automatic trace correlation
  • metricsfx - Metrics collection and export
  • httpfx - HTTP server with observability middleware

tracesfx provides the distributed tracing foundation for your observability stack, seamlessly integrating with logs and metrics to give you complete visibility into your application's behavior.

Documentation ΒΆ

Index ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

View Source
var (
	ErrFailedToCreateOTLPExporter = errors.New("failed to create OTLP trace exporter")
	ErrFailedToCreateResource     = errors.New("failed to create resource")
	ErrFailedToShutdownProvider   = errors.New("failed to shutdown trace provider")
	ErrTracesNotConfigured        = errors.New("traces not configured")
)

Functions ΒΆ

func AddCorrelationToSpan ΒΆ

func AddCorrelationToSpan(ctx context.Context, span *Span)

AddCorrelationToSpan adds correlation ID as an attribute to the span if available.

func GetCorrelationIDFromContext ΒΆ

func GetCorrelationIDFromContext(ctx context.Context) string

GetCorrelationIDFromContext extracts the correlation ID from context. This integrates with logfx's correlation system.

func GetSpanIDFromContext ΒΆ

func GetSpanIDFromContext(ctx context.Context) string

GetSpanIDFromContext extracts the span ID from the current span context.

func GetTraceIDFromContext ΒΆ

func GetTraceIDFromContext(ctx context.Context) string

GetTraceIDFromContext extracts the trace ID from the current span context. This can be used by logfx to include trace IDs in log entries.

func SetCorrelationIDInContext ΒΆ

func SetCorrelationIDInContext(ctx context.Context, correlationID string) context.Context

SetCorrelationIDInContext sets the correlation ID in context.

Types ΒΆ

type Config ΒΆ

type Config struct {
	// Service information
	ServiceName    string `conf:"service_name"    default:""`
	ServiceVersion string `conf:"service_version" default:""`

	// OpenTelemetry Collector configuration
	OTLPEndpoint string `conf:"otlp_endpoint" default:""`
	OTLPInsecure bool   `conf:"otlp_insecure" default:"true"`

	// Sampling configuration
	SampleRatio float64 `conf:"sample_ratio" default:"1.0"`

	// Batch export configuration
	BatchTimeout time.Duration `conf:"batch_timeout" default:"5s"`
	BatchSize    int           `conf:"batch_size"    default:"512"`
}

Config holds configuration for traces export.

type CorrelationIDContextKey ΒΆ

type CorrelationIDContextKey struct{}

CorrelationIDContextKey is the context key for correlation IDs. This should match the one used in logfx for seamless integration.

type Span ΒΆ

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

Span wraps an OpenTelemetry span with additional convenience methods.

func (*Span) AddEvent ΒΆ

func (s *Span) AddEvent(name string, attrs ...attribute.KeyValue)

AddEvent adds an event to the span.

func (*Span) End ΒΆ

func (s *Span) End()

End ends the span.

func (*Span) IsRecording ΒΆ

func (s *Span) IsRecording() bool

IsRecording returns true if the span is recording.

func (*Span) RecordError ΒΆ

func (s *Span) RecordError(err error, attrs ...attribute.KeyValue)

RecordError records an error as a span event.

func (*Span) SetAttributes ΒΆ

func (s *Span) SetAttributes(attrs ...attribute.KeyValue)

SetAttributes sets attributes on the span.

func (*Span) SetStatus ΒΆ

func (s *Span) SetStatus(code codes.Code, description string)

SetStatus sets the status of the span.

func (*Span) SpanContext ΒΆ

func (s *Span) SpanContext() oteltrace.SpanContext

SpanContext returns the span context.

func (*Span) Unwrap ΒΆ

func (s *Span) Unwrap() oteltrace.Span

Unwrap returns the underlying OpenTelemetry span.

type Tracer ΒΆ

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

Tracer provides a clean interface for creating and managing spans.

func (*Tracer) Start ΒΆ

func (t *Tracer) Start(
	ctx context.Context,
	name string,
	opts ...oteltrace.SpanStartOption,
) (context.Context, *Span)

Start creates a new span with the given name and options. The returned span must be ended by the caller.

func (*Tracer) StartSpanWithCorrelation ΒΆ

func (t *Tracer) StartSpanWithCorrelation(
	ctx context.Context,
	name string,
	opts ...trace.SpanStartOption,
) (context.Context, *Span)

StartSpanWithCorrelation starts a new span and automatically adds correlation ID.

type TracesProvider ΒΆ

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

TracesProvider manages OpenTelemetry tracing infrastructure.

func NewTracesProvider ΒΆ

func NewTracesProvider(config *Config) *TracesProvider

NewTracesProvider creates a new traces provider with the given configuration.

func (*TracesProvider) Init ΒΆ

func (tp *TracesProvider) Init() error

Init initializes the traces provider.

func (*TracesProvider) Shutdown ΒΆ

func (tp *TracesProvider) Shutdown(ctx context.Context) error

Shutdown gracefully shuts down the traces provider.

func (*TracesProvider) Tracer ΒΆ

func (tp *TracesProvider) Tracer(name string) *Tracer

Tracer returns a tracer with the given name.

Jump to

Keyboard shortcuts

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