tracer

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Dec 2, 2025 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package tracer provides distributed tracing functionality using OpenTelemetry.

The tracer package offers a simplified interface for implementing distributed tracing in Go applications. It abstracts away the complexity of OpenTelemetry to provide a clean, easy-to-use API for creating and managing trace spans.

Core Features:

  • Simple span creation and management
  • Error recording and status tracking
  • Customizable span attributes
  • Cross-service trace context propagation
  • Integration with OpenTelemetry backends

Basic Usage:

import (
	"context"
	"github.com/Aleph-Alpha/std/v1/tracer"
	"github.com/Aleph-Alpha/std/v1/logger"
)

// Create a logger
log, _ := logger.NewLogger(logger.Config{Level: "info"})

// Create a tracer
tracerClient := tracer.NewClient(tracer.Config{
	ServiceName:  "my-service",
	AppEnv:       "development",
	EnableExport: true,
}, log)

// Create a span
ctx, span := tracerClient.StartSpan(ctx, "process-request")
defer span.End()

// Add attributes to the span
span.SetAttributes(map[string]interface{}{
	"user.id": "123",
	"request.id": "abc-xyz",
})

// Record errors
if err != nil {
	span.RecordError(err)
	return nil, err
}

Distributed Tracing Across Services:

// In the sending service
ctx, span := tracer.StartSpan(ctx, "send-request")
defer span.End()

// Extract trace context for an outgoing HTTP request
traceHeaders := tracer.GetCarrier(ctx)
for key, value := range traceHeaders {
	req.Header.Set(key, value)
}

// In the receiving service
func httpHandler(w http.ResponseWriter, r *http.Request) {
	// Extract headers from the request
	headers := make(map[string]string)
	for key, values := range r.Header {
		if len(values) > 0 {
			headers[key] = values[0]
		}
	}

	// Create a context with the trace information
	ctx := tracer.SetCarrierOnContext(r.Context(), headers)

	// Create a child span in this service
	ctx, span := tracer.StartSpan(ctx, "handle-request")
	defer span.End()
	// ...
}

FX Module Integration:

This package provides an fx module for easy integration:

app := fx.New(
	logger.Module,
	tracer.Module,
	// ... other modules
)
app.Run()

Best Practices:

  • Create spans for significant operations in your code
  • Always defer span.End() immediately after creating a span
  • Use descriptive span names that identify the operation
  • Add relevant attributes to provide context
  • Record errors when operations fail
  • Ensure trace context is properly propagated between services

Thread Safety:

All methods on the Tracer type and Span interface are safe for concurrent use by multiple goroutines.

Index

Constants

This section is empty.

Variables

FXModule provides a Uber FX module that configures distributed tracing for your application. This module registers the tracer client with the dependency injection system and sets up proper lifecycle management to ensure graceful startup and shutdown of the tracer.

The module: 1. Provides the tracer client through the NewClient constructor 2. Registers shutdown hooks to cleanly close tracer resources on application termination

Usage:

app := fx.New(
    tracer.FXModule,
    // other modules...
)
app.Run()

This module should be included in your main application to enable distributed tracing throughout your dependency graph without manual wiring.

Functions

func RegisterTracerLifecycle

func RegisterTracerLifecycle(lc fx.Lifecycle, tracer *Tracer)

RegisterTracerLifecycle registers shutdown hooks for the tracer with the FX lifecycle. This function ensures that tracer resources are properly released when the application terminates, preventing resource leaks and ensuring traces are flushed to exporters.

Parameters:

  • lc: The FX lifecycle to register hooks with
  • tracer: The tracer instance to manage lifecycle for

The function registers an OnStop hook that: 1. Logs that the tracer is shutting down 2. Gracefully shuts down the tracer provider 3. Handles edge cases where the tracer might be nil

This function is automatically invoked by the FXModule and normally doesn't need to be called directly.

Example of how this works in the FX application lifecycle:

app := fx.New(
    tracer.FXModule,
    // When app.Stop() is called or the application receives a termination signal:
    // 1. The OnStop hook registered by RegisterTracerLifecycle is called
    // 2. The tracer is gracefully shut down, flushing any pending spans
)

Types

type Config

type Config struct {
	// ServiceName specifies the name of the service using this tracer.
	// This field is required and will appear in traces to identify the service
	// that generated the spans. It should be a descriptive, stable name that
	// uniquely identifies your service in your system architecture.
	//
	// Example values: "user-service", "payment-processor", "notification-worker"
	ServiceName string

	// AppEnv indicates the deployment environment where the service is running.
	// This helps separate traces from different environments in your observability system.
	// Common values include "development", "staging", "production".
	//
	// This field is used to set the "deployment.environment" and "environment"
	// resource attributes on all spans.
	AppEnv string

	// EnableExport controls whether traces are exported to an observability backend.
	// When set to true, the tracer will configure an OTLP HTTP exporter to send
	// traces to a collector. When false, traces are only kept in memory and not exported.
	//
	// In development environments, you might set this to false to avoid unnecessary
	// traffic to your observability system. In production, this should typically be
	// set to true to capture all telemetry data.
	//
	// Note that even when this is false, tracing is still functional for request context
	// propagation; spans just won't be sent to external systems.
	EnableExport bool
}

Config defines the configuration for the OpenTelemetry tracer. It controls service identification, environment settings, and whether traces should be exported to an observability backend.

type Span

type Span interface {
	// End completes the span and sends it to configured exporters.
	// End should be called when the operation being traced is complete.
	// It's recommended to defer this call immediately after obtaining the span.
	//
	// Example:
	//   ctx, span := tracer.StartSpan(ctx, "operation-name")
	//   defer span.End()
	End()

	// SetAttributes adds key-value pairs of attributes to the span.
	// These attributes provide additional context about the operation.
	// The method supports various data types including strings, integers,
	// floating-point numbers, and booleans.
	//
	// Example:
	//   span.SetAttributes(map[string]interface{}{
	//     "user.id": userID,
	//     "request.size": size,
	//     "request.type": "background",
	//     "priority": 3,
	//     "retry.enabled": true,
	//   })
	SetAttributes(attrs map[string]interface{})

	// RecordError marks the span as having encountered an error and
	// records the error information within the span. This helps with
	// error tracing and debugging by clearly identifying which spans
	// represent failed operations.
	//
	// Example:
	//   result, err := database.Query(ctx, query)
	//   if err != nil {
	//     span.RecordError(err)
	//     return nil, err
	//   }
	RecordError(err error)
}

Span represents a trace span for tracking operations in distributed systems. It provides methods for ending the span, recording errors, and setting attributes.

The Span interface abstracts the underlying OpenTelemetry implementation details, providing a clean API for application code to interact with spans without direct dependencies on the tracing library.

Spans represent a single operation or unit of work in your application. They form a hierarchy where a parent span can have multiple child spans, creating a trace that shows the flow of operations and their relationships.

To use a span effectively: 1. Always call End() when the operation completes (typically with defer) 2. Add attributes that provide context about the operation 3. Record any errors that occur during the operation

Spans created with StartSpan() automatically inherit the parent span from the context if one exists, creating a proper span hierarchy.

type Tracer

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

Tracer provides a simplified API for distributed tracing with OpenTelemetry. It wraps the OpenTelemetry TracerProvider and provides convenient methods for creating spans, recording errors, and propagating trace context across service boundaries.

Tracer handles the complexity of trace context propagation, span creation, and attribute management, making it easier to implement distributed tracing in your applications.

To use Tracer effectively: 1. Create spans for significant operations in your code 2. Record errors when operations fail 3. Add attributes to spans to provide context 4. Extract and inject trace context when crossing service boundaries

The Tracer is designed to be thread-safe and can be shared across goroutines.

func NewClient

func NewClient(cfg Config) (*Tracer, error)

NewClient creates and initializes a new Tracer instance with OpenTelemetry. This function sets up the OpenTelemetry tracer provider with the provided configuration, configures trace exporters if enabled, and sets global OpenTelemetry settings.

Parameters:

  • cfg: Configuration for the tracer, including service name, environment, and export settings
  • logger: Logger for recording initialization events and errors

Returns:

  • *Tracer: A configured Tracer instance ready for creating spans and managing trace context

If trace export is enabled in the configuration, this function will set up an OTLP HTTP exporter that sends traces to the configured endpoint. If export fails to initialize, it will log a fatal error.

The function also configures resource attributes for the service, including:

  • Service name
  • Deployment environment
  • Environment tag

Example:

cfg := tracer.Config{
    ServiceName: "user-service",
    AppEnv: "production",
    EnableExport: true,
}

tracerClient := tracer.NewClient(cfg, logger)

// Use the tracer in your application
ctx, span := tracerClient.StartSpan(context.Background(), "process-request")
defer span.End()

func (*Tracer) GetCarrier

func (t *Tracer) GetCarrier(ctx context.Context) map[string]string

GetCarrier extracts the current trace context from a context object and returns it as a map that can be transmitted across service boundaries. This is essential for distributed tracing to maintain trace continuity across different services.

This method extracts W3C Trace Context headers, which follow the standard format for distributed tracing propagation. Using standardized headers ensures compatibility with other services and observability tools in your infrastructure that support the W3C Trace Context specification.

When a trace spans multiple services (e.g., a web service calling a database service), the trace context must be passed between these services to maintain a unified trace. This method facilitates this by extracting that context into a format that can be added to HTTP headers, message queue properties, or other inter-service communication.

Parameters:

  • ctx: The context containing the current trace information

Returns:

  • map[string]string: A map containing the trace context headers

The returned map typically includes:

  • "traceparent": Contains trace ID, span ID, and trace flags
  • "tracestate": Contains vendor-specific trace information (if present)

Example:

// Extract trace context for an outgoing HTTP request
func makeHttpRequest(ctx context.Context, url string) (*http.Response, error) {
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }

    // Get trace headers from context
    traceHeaders := tracer.GetCarrier(ctx)

    // Add trace headers to the outgoing request
    for key, value := range traceHeaders {
        req.Header.Set(key, value)
    }

    // Make the request with trace context included
    return http.DefaultClient.Do(req)
}

func (*Tracer) SetCarrierOnContext

func (t *Tracer) SetCarrierOnContext(ctx context.Context, carrier map[string]string) context.Context

SetCarrierOnContext extracts trace information from a carrier map and injects it into a context. This is the complement to GetCarrier and is typically used when receiving requests or messages from other services that include trace headers.

This method is crucial for maintaining distributed trace continuity across service boundaries. When a service receives a request from another service that includes trace context headers, this method allows the receiving service to continue the existing trace rather than starting a new one. This creates a complete end-to-end view of the request's journey through your distributed system.

The method works with standard W3C Trace Context headers, ensuring compatibility across different services and observability systems. It handles extracting trace IDs, span IDs, and other trace context information from the carrier map and properly initializing the context for continuation of the trace.

Parameters:

  • ctx: The base context to inject trace information into
  • carrier: A map containing trace headers (like those from HTTP requests or message headers)

Returns:

  • context.Context: A new context with the trace information from the carrier injected into it

The carrier map typically contains the following trace context headers:

  • "traceparent": Contains trace ID, span ID, and trace flags
  • "tracestate": Contains vendor-specific trace information (if present)

Example:

// Extract trace context from an incoming HTTP request
func httpHandler(w http.ResponseWriter, r *http.Request) {
    // Extract headers from the request
    headers := make(map[string]string)
    for key, values := range r.Header {
        if len(values) > 0 {
            headers[key] = values[0]
        }
    }

    // Create a context with the trace information
    ctx := tracer.SetCarrierOnContext(r.Context(), headers)

    // Use this traced context for processing the request
    // Any spans created with this context will be properly connected to the trace
    result, err := processRequest(ctx, r)
    // ...
}

func (*Tracer) StartSpan

func (t *Tracer) StartSpan(ctx context.Context, name string) (context.Context, Span)

StartSpan creates a new span with the given name and returns an updated context containing the span, along with a Span interface. This is the primary method for creating spans to trace operations in your application.

The created span becomes a child of any span that exists in the provided context. If no span exists in the context, a new root span is created. This automatically builds a hierarchy of spans that reflects the structure of your application's operations.

Parameters:

  • ctx: The parent context, which may contain a parent span
  • name: A descriptive name for the operation being traced (e.g., "http-request", "db-query")

Returns:

  • context.Context: A new context containing the created span
  • Span: An interface to interact with the span, which must be ended when the operation completes

Best practices:

  • Use descriptive, consistent naming conventions for spans
  • Create spans for operations that are significant for performance or debugging
  • For operations with sub-operations, create a parent span for the overall operation and child spans for each sub-operation
  • Always defer span.End() immediately after creating the span

Example:

func processRequest(ctx context.Context, req Request) (Response, error) {
    // Create a span for this operation
    ctx, span := tracer.StartSpan(ctx, "process-request")
    // Ensure the span is ended when the function returns
    defer span.End()

    // Add context information to the span
    span.SetAttributes(map[string]interface{}{
        "request.id": req.ID,
        "request.type": req.Type,
        "user.id": req.UserID,
    })

    // Perform the operation, using the context with the span
    result, err := performWork(ctx, req)
    if err != nil {
        // Record the error on the span
        span.RecordError(err)
        return Response{}, err
    }

    return result, nil
}

Jump to

Keyboard shortcuts

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