otel

package
v2.7.2 Latest Latest
Warning

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

Go to latest
Published: Nov 24, 2025 License: MIT Imports: 19 Imported by: 0

README

OpenTelemetry Integration

Go Reference

Unified OpenTelemetry v2 configuration and instrumentation utilities for the pkg library ecosystem.

Overview

The otel package provides centralized OpenTelemetry configuration that enables observability across all library packages. It supports the three pillars of observability:

  • Traces - Distributed tracing for request flows
  • Metrics - Performance and health measurements
  • Logs - Structured logging via OpenTelemetry standard

Features

  • Unified Configuration: Single config object for all telemetry pillars
  • Selective Enablement: Enable only the telemetry you need
  • No-op by Default: Zero overhead when providers are not configured
  • Method Chaining: Fluent API for configuration
  • Standard Logging Helper: OTel-aware logging with automatic trace correlation
  • OTLP Logging Support: Export logs to OpenTelemetry collectors with flexible options
  • Granular Log Levels: Fine-grained control over log verbosity (debug, info, warn, error, none)
  • Graceful Shutdown: Proper resource cleanup

Installation

go get github.com/jasoet/pkg/v2/otel

Quick Start

Basic Configuration
package main

import (
    "context"
    "github.com/jasoet/pkg/v2/otel"
    "go.opentelemetry.io/otel/sdk/trace"
    "go.opentelemetry.io/otel/sdk/metric"
)

func main() {
    // Create tracer and meter providers (your setup)
    tracerProvider := trace.NewTracerProvider(/* ... */)
    meterProvider := metric.NewMeterProvider(/* ... */)

    // Create unified OTel config
    otelConfig := otel.NewConfig("my-service").
        WithTracerProvider(tracerProvider).
        WithMeterProvider(meterProvider).
        WithServiceVersion("1.0.0")

    // Use with library packages
    // server.Start(server.Config{OTelConfig: otelConfig, ...})
    // db.Pool(db.Config{OTelConfig: otelConfig, ...})

    // Cleanup on shutdown
    defer otelConfig.Shutdown(context.Background())
}
Selective Telemetry

Enable only what you need:

// Tracing only
cfg := otel.NewConfig("my-service").
    WithTracerProvider(tracerProvider).
    WithoutLogging()  // Disable default logging

// Metrics only
cfg := otel.NewConfig("my-service").
    WithMeterProvider(meterProvider).
    WithoutLogging()

// All three pillars
cfg := otel.NewConfig("my-service").
    WithTracerProvider(tracerProvider).
    WithMeterProvider(meterProvider).
    WithLoggerProvider(loggerProvider)
Custom Logger Provider

Use the logging package for better formatting and automatic trace correlation:

import (
    "github.com/jasoet/pkg/v2/logging"
    "github.com/jasoet/pkg/v2/otel"
)

// Production-ready logger with trace correlation
loggerProvider := logging.NewLoggerProvider("my-service", false)

cfg := otel.NewConfig("my-service").
    WithTracerProvider(tracerProvider).
    WithMeterProvider(meterProvider).
    WithLoggerProvider(loggerProvider)
OTLP Logging with Flexible Options

Create a logger provider with OTLP export and granular control:

import "github.com/jasoet/pkg/v2/otel"

// Console-only logging (default, no OTLP)
loggerProvider, err := otel.NewLoggerProviderWithOptions("my-service")

// OTLP logging with console output (local development)
loggerProvider, err := otel.NewLoggerProviderWithOptions(
    "my-service",
    otel.WithOTLPEndpoint("localhost:4318", true), // insecure for local
    otel.WithConsoleOutput(true),
    otel.WithLogLevel(logging.LogLevelInfo),
)

// OTLP-only logging (production)
loggerProvider, err := otel.NewLoggerProviderWithOptions(
    "my-service",
    otel.WithOTLPEndpoint("otel-collector.prod:4318", false), // secure
    otel.WithConsoleOutput(false), // disable console in prod
    otel.WithLogLevel(logging.LogLevelWarn),
)

// Use with OTel config
cfg := otel.NewConfig("my-service").
    WithTracerProvider(tracerProvider).
    WithLoggerProvider(loggerProvider)

Configuration API

Config Struct
type Config struct {
    TracerProvider trace.TracerProvider  // nil = no tracing
    MeterProvider  metric.MeterProvider  // nil = no metrics
    LoggerProvider log.LoggerProvider    // nil = no OTel logs
    ServiceName    string
    ServiceVersion string
}
Builder Methods
Method Description
NewConfig(name) Create config with service name and default logger
WithTracerProvider(tp) Enable distributed tracing
WithMeterProvider(mp) Enable metrics collection
WithLoggerProvider(lp) Set custom logger provider
WithServiceVersion(v) Set service version
WithoutLogging() Disable default stdout logging
Helper Methods
// Check what's enabled
cfg.IsTracingEnabled()  // bool
cfg.IsMetricsEnabled()  // bool
cfg.IsLoggingEnabled()  // bool

// Get instrumentation components
tracer := cfg.GetTracer("scope-name")   // Returns no-op if disabled
meter := cfg.GetMeter("scope-name")     // Returns no-op if disabled
logger := cfg.GetLogger("scope-name")   // Returns no-op if disabled

// Context management (recommended)
ctx = otel.ContextWithConfig(ctx, cfg)  // Store config in context
cfg = otel.ConfigFromContext(ctx)       // Retrieve config from context

// Cleanup
cfg.Shutdown(context.Background())
Logger Provider Options

Create flexible logger providers with NewLoggerProviderWithOptions:

Option Description
WithOTLPEndpoint(endpoint, insecure) Enable OTLP log export to collector
WithConsoleOutput(enabled) Enable/disable console logging (default: true)
WithLogLevel(level) Set log level: LogLevelDebug, LogLevelInfo, LogLevelWarn, LogLevelError, LogLevelNone

Log Level Priority:

  1. Explicit WithLogLevel() (highest priority)
  2. Default to info level

Examples:

import "github.com/jasoet/pkg/v2/logging"

// Default info level
provider, _ := otel.NewLoggerProviderWithOptions("service")

// Debug mode (all logs)
provider, _ := otel.NewLoggerProviderWithOptions("service",
    otel.WithLogLevel(logging.LogLevelDebug))

// Specific log level
provider, _ := otel.NewLoggerProviderWithOptions("service",
    otel.WithLogLevel(logging.LogLevelWarn))

// OTLP + console for development
provider, _ := otel.NewLoggerProviderWithOptions("service",
    otel.WithOTLPEndpoint("localhost:4318", true),
    otel.WithConsoleOutput(true),
    otel.WithLogLevel(logging.LogLevelDebug))

// OTLP-only for production
provider, _ := otel.NewLoggerProviderWithOptions("service",
    otel.WithOTLPEndpoint("collector:4318", false),
    otel.WithConsoleOutput(false),
    otel.WithLogLevel(logging.LogLevelInfo))

Standard Logging Helper

The otel package provides LogHelper for OTel-aware logging with automatic log-span correlation:

import "github.com/jasoet/pkg/v2/otel"

// Create a logger (uses OTel when configured, falls back to zerolog otherwise)
logger := otel.NewLogHelper(ctx, otelConfig, "github.com/jasoet/pkg/v2/mypackage", "mypackage.DoWork")

// Log with automatic trace_id/span_id injection (when OTel is enabled)
logger.Debug("Starting work", "workerId", 123)
logger.Info("Work completed", "duration", elapsed)
logger.Error(err, "Work failed", "workerId", 123)

Benefits:

  • Automatic trace_id/span_id injection when OTel is configured
  • Graceful fallback to zerolog when OTel is not configured
  • Consistent API across all packages
  • Errors automatically recorded in active spans

See helper.go for full documentation.

Context-Based Config Propagation

The recommended pattern for passing OTel config through your application layers is to store it in the context once at the entry point:

import "github.com/jasoet/pkg/v2/otel"

// At the HTTP handler entry point
func (h *Handler) HandleRequest(c echo.Context) error {
    // Store config in context once
    ctx := otel.ContextWithConfig(c.Request().Context(), h.otelConfig)

    // Config automatically available to all nested operations
    return h.service.ProcessRequest(ctx, req)
}

// In service layer - no need to pass config explicitly
func (s *Service) ProcessRequest(ctx context.Context, req Request) error {
    // Config retrieved from context automatically
    // Fields passed here are automatically included in all log calls
    lc := otel.Layers.StartService(ctx, "user", "ProcessRequest",
        otel.F("request.id", req.ID))
    defer lc.End()

    // Logger is always available (zerolog fallback when no config)
    // Fields "layer=service" and "request.id" are automatically included
    lc.Logger.Info("Processing request")

    return s.repo.Save(lc.Context(), data)
}

// In repository layer - config still available
func (r *Repository) Save(ctx context.Context, data Data) error {
    lc := otel.Layers.StartRepository(ctx, "user", "Save",
        otel.F("data.id", data.ID))
    defer lc.End()

    // Fields "layer=repository" and "data.id" automatically in logs
    lc.Logger.Debug("Saving to database")

    lc.Success("Data saved")
    return nil
}

Benefits:

  • Set config once at entry point, available everywhere
  • No need to pass config as parameter through all layers
  • Natural propagation through context (like span data)
  • Clean API - fewer parameters
  • Logger always available (zerolog fallback when no config)
  • Fields automatically included in all log calls

API Pattern:

// Store config in context (once at entry point)
ctx = otel.ContextWithConfig(ctx, cfg)

// Create layer contexts - all return both Span and Logger
// Fields passed here are automatically included in all log calls
lc := otel.Layers.StartHandler(ctx, "user", "GetUser", otel.F("http.method", "GET"))
lc := otel.Layers.StartService(ctx, "user", "CreateUser", otel.F("user.email", email))
lc := otel.Layers.StartRepository(ctx, "user", "FindByID", otel.F("user.id", id))
lc := otel.Layers.StartOperations(ctx, "user", "ProcessQueue", otel.F("queue.name", queue))
lc := otel.Layers.StartMiddleware(ctx, "auth", "ValidateToken", otel.F("token.type", "JWT"))

// All log calls automatically include the fields
lc.Logger.Info("Processing")           // Includes all fields
lc.Logger.Debug("Details", F("extra", val)) // Adds extra field
lc.Error(err, "Failed")               // Includes all fields
lc.Success("Done")                    // Includes all fields

// Get logger from span (config retrieved automatically)
span := otel.StartSpan(ctx, "service.user", "DoWork")
logger := span.Logger("service.user") // No config parameter needed

Integration Examples

HTTP Server
import (
    "github.com/jasoet/pkg/v2/otel"
    "github.com/jasoet/pkg/v2/server"
)

otelConfig := otel.NewConfig("my-api").
    WithTracerProvider(tracerProvider).
    WithMeterProvider(meterProvider)

server.Start(server.Config{
    Port:       8080,
    OTelConfig: otelConfig,
})
gRPC Server
import (
    "github.com/jasoet/pkg/v2/otel"
    "github.com/jasoet/pkg/v2/grpc"
)

otelConfig := otel.NewConfig("my-grpc-service").
    WithTracerProvider(tracerProvider).
    WithMeterProvider(meterProvider)

grpcServer := grpc.NewServer(
    grpc.NewConfig("my-service", 9090).
        WithOTelConfig(otelConfig),
)
Database
import (
    "github.com/jasoet/pkg/v2/otel"
    "github.com/jasoet/pkg/v2/db"
)

otelConfig := otel.NewConfig("my-db-service").
    WithTracerProvider(tracerProvider).
    WithMeterProvider(meterProvider)

pool, _ := db.ConnectionConfig{
    DbType:     db.Postgresql,
    Host:       "localhost",
    OTelConfig: otelConfig,
}.Pool()

// All queries are automatically traced
pool.Find(&users)
REST Client
import (
    "github.com/jasoet/pkg/v2/otel"
    "github.com/jasoet/pkg/v2/rest"
)

otelConfig := otel.NewConfig("my-client").
    WithTracerProvider(tracerProvider).
    WithMeterProvider(meterProvider)

client := rest.NewClient(rest.ClientConfig{
    BaseURL:    "https://api.example.com",
    OTelConfig: otelConfig,
})

// Requests are automatically traced
client.Get("/users", &result)

Complete Example

See the fullstack OTel example for a complete application demonstrating all three telemetry pillars across multiple packages.

Testing

The package includes comprehensive tests with 97.1% coverage:

# Run tests
go test ./otel -v

# With coverage
go test ./otel -cover
Test Utilities

Use no-op providers for testing:

import (
    "github.com/jasoet/pkg/v2/otel"
    noopm "go.opentelemetry.io/otel/metric/noop"
    noopt "go.opentelemetry.io/otel/trace/noop"
)

func TestMyCode(t *testing.T) {
    cfg := otel.NewConfig("test-service").
        WithTracerProvider(noopt.NewTracerProvider()).
        WithMeterProvider(noopm.NewMeterProvider()).
        WithoutLogging()

    // Test your code with cfg
}

Best Practices

1. Create Once, Share Everywhere
// ✅ Good: Single config shared across packages
otelConfig := otel.NewConfig("my-service").
    WithTracerProvider(tp).
    WithMeterProvider(mp)

serverCfg := server.Config{OTelConfig: otelConfig}
dbCfg := db.Config{OTelConfig: otelConfig}
2. Always Shutdown
// ✅ Good: Graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

if err := otelConfig.Shutdown(ctx); err != nil {
    log.Printf("OTel shutdown error: %v", err)
}
3. Check Before Using
// ✅ Good: Check enablement
if cfg.IsTracingEnabled() {
    tracer := cfg.GetTracer("my-scope")
    // Use tracer
}
4. Use LogHelper for Consistent Logging
// ✅ Good: Use otel.LogHelper for automatic log-span correlation
logger := otel.NewLogHelper(ctx, otelConfig, "github.com/jasoet/pkg/v2/mypackage", "mypackage.DoWork")
logger.Info("Work completed", "duration", elapsed)

Architecture

Design Principles
  1. Zero Dependencies: Only depends on OTel SDK (no custom exporters)
  2. No-op Safety: Nil providers result in no-op implementations
  3. Lazy Initialization: Providers created only when needed
  4. Immutable Config: Thread-safe after creation
Package Structure
otel/
├── config.go        # Config struct and builder methods
├── config_test.go   # Config tests
├── logging.go       # OTLP logger provider with flexible options
├── logging_test.go  # Logger provider tests
├── helper.go        # Standard logging helper with OTel integration
├── helper_test.go   # LogHelper tests
├── instrumentation.go        # Instrumentation utilities
├── instrumentation_test.go   # Instrumentation tests
└── doc.go          # Package documentation

Troubleshooting

No Telemetry Data

Problem: Not seeing traces/metrics/logs

Solutions:

// 1. Check if enabled
fmt.Println("Tracing:", cfg.IsTracingEnabled())
fmt.Println("Metrics:", cfg.IsMetricsEnabled())
fmt.Println("Logging:", cfg.IsLoggingEnabled())

// 2. Verify providers are set
if cfg.TracerProvider == nil {
    // Tracing will be no-op
}

// 3. Ensure shutdown is called
defer cfg.Shutdown(context.Background())
Default Logger Too Verbose

Problem: Stdout logger creating too much output

Solution:

// Disable default logger
cfg := otel.NewConfig("my-service").WithoutLogging()

// Or use custom logger
cfg := otel.NewConfig("my-service").
    WithLoggerProvider(myLoggerProvider)
Provider Already Registered

Problem: Global provider conflicts

Solution: This package doesn't use global providers - it returns scoped instruments from GetTracer(), GetMeter(), and GetLogger().

Version Compatibility

  • OpenTelemetry: v1.38.0+
  • Go: 1.25+
  • pkg library: v2.0.0+

Migration from v1

v2 uses OpenTelemetry v2 API:

// v1 (OTel v1)
import "go.opentelemetry.io/otel"
tracer := otel.Tracer("my-scope")

// v2 (OTel v2)
import "github.com/jasoet/pkg/v2/otel"
cfg := otel.NewConfig("my-service").WithTracerProvider(tp)
tracer := cfg.GetTracer("my-scope")

See VERSIONING_GUIDE.md for complete migration guide.

  • logging - Structured logging with OTel integration
  • server - HTTP server with automatic tracing
  • grpc - gRPC server with automatic instrumentation
  • db - Database with query tracing
  • rest - REST client with distributed tracing

License

MIT License - see LICENSE for details.

Documentation

Overview

Package otel provides OpenTelemetry instrumentation utilities for github.com/jasoet/pkg/v2.

This package offers:

  • Centralized configuration for traces, metrics, and logs
  • Library-specific semantic conventions
  • No-op implementations when telemetry is disabled
  • Integrated span and logging with automatic correlation
  • Layer-aware instrumentation (Handler, Operations, Service, Repository)

Configuration

Create an otel.Config with the desired providers:

cfg := &otel.Config{
    TracerProvider: tracerProvider,  // optional
    MeterProvider:  meterProvider,   // optional
    LoggerProvider: loggerProvider,  // optional
    ServiceName:    "my-service",
    ServiceVersion: "1.0.0",
}

Then pass this config to package configurations (server.Config, grpc options, etc.).

Telemetry Pillars

Enable any combination of:

  • Traces (distributed tracing)
  • Metrics (measurements and aggregations)
  • Logs (structured log export via OpenTelemetry standard)

Each pillar is independently controlled by setting its provider. Nil providers result in no-op implementations with zero overhead.

Unified Layer Instrumentation

Use LayerContext for simplified span + logging with automatic correlation:

// Service layer example
lc := otel.Layers.StartService(ctx, cfg, "user", "CreateUser",
    "user.id", userID)
defer lc.End()

lc.Logger.Info("Creating user", otel.F("email", email))
if err := repo.Save(lc.Context(), data); err != nil {
    return lc.Error(err, "save failed")
}
return lc.Success("User created")

Available layers: StartHandler, StartOperations, StartService, StartRepository

Standard Logging Helper

This package provides otel.LogHelper for OTel-aware logging that automatically correlates logs with traces. It uses OTel LoggerProvider when available, otherwise falls back to zerolog. Logs automatically include trace_id and span_id when a span is active. See helper.go for details.

Example (ConfigOptionalButRecommended)

Example_configOptionalButRecommended demonstrates that config is optional but recommended.

package main

import (
	"context"
	"fmt"

	"github.com/jasoet/pkg/v2/otel"
)

func main() {
	ctx := context.Background()

	// Option 1: Without config (uses zerolog fallback)
	simpleApp := otel.Layers.StartService(ctx, "simple", "DoWork")
	defer simpleApp.End()
	// Logger available with zerolog fallback
	simpleApp.Logger.Info("Simple app - basic logging")

	// Option 2: With config (full observability)
	cfg := otel.NewConfig("production-service")
	ctx = otel.ContextWithConfig(ctx, cfg)
	productionApp := otel.Layers.StartService(ctx, "user", "ProcessOrder")
	defer productionApp.End()
	// Logger with OTel integration for production
	productionApp.Logger.Info("Production app - full observability")

	// Recommendation: Always pass config for production to enable:
	// - Automatic trace correlation
	// - Service name in logs
	// - Easy tracing integration later
	// - Consistent log formatting

	fmt.Println("Both patterns work, config recommended for production")
}
Example (GradualOTelAdoption)

Example_gradualOTelAdoption shows how to add OTel config to an existing app.

package main

import (
	"context"
	"fmt"

	"github.com/jasoet/pkg/v2/otel"
)

func main() {
	ctx := context.Background()

	// Phase 1: Start without config (uses zerolog fallback)
	lc1 := otel.Layers.StartService(ctx, "user", "CreateUser")
	defer lc1.End()
	// Logger uses zerolog fallback
	lc1.Logger.Info("Phase 1: Basic logging")

	// Phase 2: Add OTel config via context
	cfg := otel.NewConfig("my-service")
	ctx = otel.ContextWithConfig(ctx, cfg)
	// Later: cfg = cfg.WithTracerProvider(tp)

	lc2 := otel.Layers.StartService(ctx, "user", "CreateUser")
	defer lc2.End()
	// Logger now uses OTel with trace correlation
	lc2.Logger.Info("Phase 2: OTel integration added")

	fmt.Println("Gradual OTel adoption completed")
}
Example (LayerContextIntegration)

Example demonstrates the new integrated span-logging features

package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/jasoet/pkg/v2/otel"
)

func main() {
	// Setup OTel config
	cfg := otel.NewConfig("example-service")

	// Store config in context for automatic propagation
	ctx := otel.ContextWithConfig(context.Background(), cfg)

	// Example 1: Using LayerContext for simplified span + logging
	// Fields passed here are automatically included in all log calls
	fmt.Println("=== Example 1: LayerContext ===")
	lc := otel.Layers.StartService(ctx, "user", "CreateUser",
		otel.F("user.id", "12345"))
	defer lc.End()

	// Logger is always available, fields auto-included
	lc.Logger.Info("Creating user", otel.F("email", "user@example.com"))
	// Simulate success - user.id="12345" automatically included
	lc.Success("User created successfully")

	// Example 2: SpanHelper with Logger() method
	fmt.Println("\n=== Example 2: SpanHelper.Logger() ===")
	span := otel.StartSpan(ctx, "service.order", "ProcessOrder",
		otel.WithAttribute("order.id", "ORD-123"))
	defer span.End()

	// Logger is always available
	logger := span.Logger("service.order")
	logger.Info("Processing order", otel.F("items", 3))

	// Example 3: LogEvent for dual span events + logs
	fmt.Println("\n=== Example 3: LogEvent ===")
	span2 := otel.StartSpan(ctx, "service.cache", "GetFromCache",
		otel.WithAttribute("cache.key", "user:123"))
	defer span2.End()

	logger2 := span2.Logger("service.cache")
	span2.LogEvent(logger2, "cache.miss",
		otel.F("key", "user:123"),
		otel.F("reason", "expired"))

	// Example 4: Error handling with LayerContext
	fmt.Println("\n=== Example 4: Error Handling ===")
	lc2 := otel.Layers.StartRepository(ctx, "user", "FindByID",
		otel.F("user.id", "999"))
	defer lc2.End()

	// Simulate error
	err := errors.New("user not found")
	_ = lc2.Error(err, "failed to find user", otel.F("user.id", "999"))

	// Example 5: All five layers (config propagates automatically via context)
	// Fields are automatically included in all log calls for each layer
	fmt.Println("\n=== Example 5: All Layers ===")

	// Middleware layer (auth, CORS, rate limiting, etc.)
	middlewareCtx := otel.Layers.StartMiddleware(ctx, "auth", "ValidateToken",
		otel.F("http.path", "/api/users"),
		otel.F("http.method", "GET"))
	defer middlewareCtx.End()
	middlewareCtx.Logger.Info("Validating authentication token")

	// Handler layer (config available from middleware.Context())
	handlerCtx := otel.Layers.StartHandler(middlewareCtx.Context(), "user", "GetUser",
		otel.F("http.method", "GET"))
	defer handlerCtx.End()
	handlerCtx.Logger.Info("Handling request")

	// Operations layer (config available from handler.Context())
	opsCtx := otel.Layers.StartOperations(handlerCtx.Context(), "user", "ProcessUserRequest")
	defer opsCtx.End()
	opsCtx.Logger.Info("Orchestrating user request")

	// Service layer
	serviceCtx := otel.Layers.StartService(opsCtx.Context(), "user", "GetUser",
		otel.F("user.id", "123"))
	defer serviceCtx.End()
	serviceCtx.Logger.Info("Fetching user data")

	// Repository layer
	repoCtx := otel.Layers.StartRepository(serviceCtx.Context(), "user", "FindByID",
		otel.F("user.id", "123"),
		otel.F("db.operation", "select"))
	defer repoCtx.End()
	repoCtx.Logger.Debug("Querying database")
	repoCtx.Success("User found")

	fmt.Println("\nAll examples completed successfully")

}
Output:

=== Example 1: LayerContext ===

=== Example 2: SpanHelper.Logger() ===

=== Example 3: LogEvent ===

=== Example 4: Error Handling ===

=== Example 5: All Layers ===

All examples completed successfully
Example (LayerPropagation)

Example_layerPropagation demonstrates context propagation through layers. Config stored in context once is automatically available to all nested layers. Fields passed to each layer are automatically included in all log calls.

package main

import (
	"context"
	"fmt"

	"github.com/jasoet/pkg/v2/otel"
)

func main() {
	// Single config instance stored in context once
	cfg := otel.NewConfig("my-service")
	ctx := otel.ContextWithConfig(context.Background(), cfg)

	// Handler layer - receives HTTP request
	// Fields automatically included in all handler logs
	handler := otel.Layers.StartHandler(ctx, "user", "CreateUser",
		otel.F("http.method", "POST"),
		otel.F("http.path", "/users"))
	defer handler.End()

	// Logs include: layer="handler", http.method="POST", http.path="/users"
	handler.Logger.Info("HTTP request received")

	// Operations layer - config automatically available from context
	ops := otel.Layers.StartOperations(handler.Context(), "user", "CreateUserOperation")
	defer ops.End()

	// Logs include: layer="operations"
	ops.Logger.Info("Validating request")

	// Service layer - config still available
	service := otel.Layers.StartService(ops.Context(), "user", "CreateUser",
		otel.F("user.email", "user@example.com"))
	defer service.End()

	// Logs include: layer="service", user.email="user@example.com"
	service.Logger.Info("Creating user entity")

	// Repository layer - config still available
	repo := otel.Layers.StartRepository(service.Context(), "user", "Insert",
		otel.F("db.operation", "insert"),
		otel.F("db.table", "users"))
	defer repo.End()

	// Logs include: layer="repository", db.operation="insert", db.table="users"
	repo.Logger.Info("Inserting into database")
	repo.Success("User inserted")

	// All logs will be correlated with trace_id and span_id
	fmt.Println("Request completed with full trace")
}
Example (LogHelperSpanAccessor)

Example showing LogHelper.Span() accessor

package main

import (
	"context"

	"github.com/jasoet/pkg/v2/otel"
)

func main() {
	cfg := otel.NewConfig("test-service")
	ctx := otel.ContextWithConfig(context.Background(), cfg)

	span := otel.StartSpan(ctx, "service.test", "Operation")
	defer span.End()

	logger := span.Logger("service.test")

	// Access span from logger
	if logger != nil && logger.Span().IsRecording() {
		logger.Info("Span is active")
	}

}
Example (MiddlewareLayer)

Example showing middleware layer instrumentation

package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/jasoet/pkg/v2/otel"
)

func main() {
	cfg := otel.NewConfig("api-service")
	ctx := otel.ContextWithConfig(context.Background(), cfg)

	fmt.Println("=== Middleware Layer Examples ===")

	// Example 1: Authentication middleware
	// Fields automatically included in all auth logs
	fmt.Println("\n--- Authentication Middleware ---")
	authCtx := otel.Layers.StartMiddleware(ctx, "auth", "ValidateToken",
		otel.F("http.path", "/api/users"),
		otel.F("http.method", "GET"))
	defer authCtx.End()

	authCtx.Logger.Info("Checking authorization header")
	// Simulate successful auth - http.path and http.method auto-included
	authCtx.Success("Token validated", otel.F("user.id", "user-123"))

	// Example 2: CORS middleware
	fmt.Println("\n--- CORS Middleware ---")
	corsCtx := otel.Layers.StartMiddleware(ctx, "cors", "SetHeaders",
		otel.F("origin", "https://example.com"))
	defer corsCtx.End()

	corsCtx.Logger.Info("Setting CORS headers")
	corsCtx.Success("CORS headers configured")

	// Example 3: Rate limiting middleware with error
	fmt.Println("\n--- Rate Limiting Middleware ---")
	rateLimitCtx := otel.Layers.StartMiddleware(ctx, "ratelimit", "CheckLimit",
		otel.F("client.ip", "192.168.1.100"),
		otel.F("endpoint", "/api/data"))
	defer rateLimitCtx.End()

	rateLimitCtx.Logger.Warn("Rate limit exceeded", otel.F("limit", 100))
	err := errors.New("rate limit exceeded")
	_ = rateLimitCtx.Error(err, "request throttled", otel.F("retry_after", "60s"))

	// Example 4: Middleware chain with context propagation
	fmt.Println("\n--- Middleware Chain ---")
	mw1Ctx := otel.Layers.StartMiddleware(ctx, "logging", "RequestLogger")
	defer mw1Ctx.End()
	mw1Ctx.Logger.Info("Incoming request", otel.F("request.id", "req-456"))

	// Next middleware gets context from previous one
	mw2Ctx := otel.Layers.StartMiddleware(mw1Ctx.Context(), "validation", "ValidateInput")
	defer mw2Ctx.End()
	mw2Ctx.Logger.Info("Validating request body")
	mw2Ctx.Success("Validation passed")
	mw1Ctx.Success("Request logged")

	fmt.Println("\nAll middleware examples completed")

}
Output:

=== Middleware Layer Examples ===

--- Authentication Middleware ---

--- CORS Middleware ---

--- Rate Limiting Middleware ---

--- Middleware Chain ---

All middleware examples completed
Example (OptionalFunctionParameter)

Example showing optional function parameter

package main

import (
	"context"

	"github.com/jasoet/pkg/v2/otel"
)

func main() {
	cfg := otel.NewConfig("test-service")
	ctx := context.Background()

	// With function name
	logger1 := otel.NewLogHelper(ctx, cfg, "mypackage", "MyFunction")
	logger1.Info("Message with function", otel.F("key", "value"))

	// Without function name (when used with spans)
	logger2 := otel.NewLogHelper(ctx, cfg, "mypackage", "")
	logger2.Info("Message without function", otel.F("key", "value"))

}
Example (WithOTelConfig)

Example_withOTelConfig demonstrates full OTel integration with tracing and structured logging.

package main

import (
	"context"
	"fmt"

	"github.com/jasoet/pkg/v2/otel"
)

func main() {
	// Create OTel config with default logger (zerolog-based with OTel integration)
	cfg := otel.NewConfig("my-service")

	// Add TracerProvider here if you have one
	// cfg = cfg.WithTracerProvider(tracerProvider)

	// Store config in context for automatic propagation
	ctx := otel.ContextWithConfig(context.Background(), cfg)

	// Fields passed here are automatically included in all log calls
	lc := otel.Layers.StartService(ctx, "user", "CreateUser",
		otel.F("user.id", "12345"))
	defer lc.End()

	// Logs include trace_id/span_id automatically when spans are active
	// Fields "layer=service" and "user.id=12345" are automatically included
	lc.Logger.Info("Creating user with OTel", otel.F("email", "user@example.com"))

	lc.Success("User created successfully")

	fmt.Println("OTel integration active")
}
Example (WithoutOTelConfig)

Example_withoutOTelConfig demonstrates span creation without OTel configuration. Logger is always available with zerolog fallback when no config is in context.

package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/jasoet/pkg/v2/otel"
)

func main() {
	ctx := context.Background()

	// No config in context - spans work, logger uses zerolog fallback
	// Fields passed here are automatically included in all log calls
	lc := otel.Layers.StartService(ctx, "user", "CreateUser",
		otel.F("user.id", "12345"))
	defer lc.End()

	// Logger is always available (zerolog fallback without OTel config)
	// All logs automatically include: layer="service", user.id="12345"
	lc.Logger.Info("Creating user")

	// Span tracking still works
	lc.Span.AddAttribute("status", "processing")

	// Error handling works (recorded in span and log)
	err := errors.New("validation failed")
	if err != nil {
		_ = lc.Error(err, "User creation failed")
		return
	}

	lc.Success("User created successfully")

	fmt.Println("Spans and logging work without OTel config")
}

Index

Examples

Constants

This section is empty.

Variables

View Source
var Layers = &LayeredSpanHelper{}

Layers provides convenience methods for creating layer-specific spans

Functions

func ContextWithConfig added in v2.6.0

func ContextWithConfig(ctx context.Context, cfg *Config) context.Context

ContextWithConfig stores the OTel config in the context. This allows nested layers to access the config without explicit parameter passing.

Example:

func (h *Handler) Handle(c echo.Context) error {
    ctx := otel.ContextWithConfig(c.Request().Context(), cfg)
    return h.service.DoWork(ctx) // Service can now access config
}

func NewLoggerProviderWithOptions added in v2.4.8

func NewLoggerProviderWithOptions(serviceName string, opts ...LoggerProviderOption) (log.LoggerProvider, error)

NewLoggerProviderWithOptions creates a LoggerProvider with flexible options. It supports both console output (zerolog) and OTLP export, or both simultaneously.

Parameters:

  • serviceName: Name of the service
  • opts: Optional configuration options

Returns:

  • A log.LoggerProvider configured according to the options
  • An error if OTLP exporter creation fails

Example:

provider, err := otel.NewLoggerProviderWithOptions("my-service",
    otel.WithLogLevel(logging.LogLevelDebug),
    otel.WithOTLPEndpoint("localhost:4318", true),
    otel.WithConsoleOutput(true))

Types

type Config

type Config struct {
	// TracerProvider for distributed tracing
	// If nil, tracing will be disabled (no-op tracer)
	TracerProvider trace.TracerProvider

	// MeterProvider for metrics collection
	// If nil, metrics will be disabled (no-op meter)
	MeterProvider metric.MeterProvider

	// LoggerProvider for structured logging via OTel
	// Defaults to zerolog-based provider when using NewConfig()
	// Set to nil explicitly to disable logging
	LoggerProvider log.LoggerProvider

	// ServiceName identifies the service in telemetry data
	ServiceName string

	// ServiceVersion identifies the service version
	ServiceVersion string
}

Config holds OpenTelemetry configuration for instrumentation. TracerProvider and MeterProvider are optional - nil values result in no-op implementations. LoggerProvider defaults to zerolog-based provider when using NewConfig().

func ConfigFromContext added in v2.6.0

func ConfigFromContext(ctx context.Context) *Config

ConfigFromContext retrieves the OTel config from context. Returns nil if no config is stored in the context.

Example:

func (s *Service) DoWork(ctx context.Context) error {
    cfg := otel.ConfigFromContext(ctx)
    if cfg != nil {
        span := otel.StartSpan(ctx, "service", "DoWork")
        defer span.End()
        logger := span.Logger("service")
        // ... use logger
    }
}

func NewConfig

func NewConfig(serviceName string) *Config

NewConfig creates a new OpenTelemetry configuration with default LoggerProvider. The default LoggerProvider uses zerolog with automatic log-span correlation for production use. Use With* methods to add TracerProvider and MeterProvider.

Example:

cfg := otel.NewConfig("my-service").
    WithTracerProvider(tp).
    WithMeterProvider(mp)

For custom logger configuration:

import "github.com/jasoet/pkg/v2/logging"
cfg := &otel.Config{
    ServiceName:    "my-service",
    LoggerProvider: logging.NewLoggerProvider("my-service", true), // enable debug mode
}
cfg.WithTracerProvider(tp).WithMeterProvider(mp)

func (*Config) DisableLogging added in v2.6.2

func (c *Config) DisableLogging() *Config

DisableLogging disables logging by setting LoggerProvider to nil

func (*Config) DisableMetrics added in v2.6.2

func (c *Config) DisableMetrics() *Config

DisableMetrics disables metrics by setting MeterProvider to nil

func (*Config) DisableTracing added in v2.6.2

func (c *Config) DisableTracing() *Config

DisableTracing disables tracing by setting TracerProvider to nil

func (*Config) GetLogger

func (c *Config) GetLogger(scopeName string, opts ...log.LoggerOption) log.Logger

GetLogger returns a logger for the given instrumentation scope. Returns a no-op logger if logging is not configured.

func (*Config) GetMeter

func (c *Config) GetMeter(scopeName string, opts ...metric.MeterOption) metric.Meter

GetMeter returns a meter for the given instrumentation scope. Returns a no-op meter if metrics are not configured.

func (*Config) GetTracer

func (c *Config) GetTracer(scopeName string, opts ...trace.TracerOption) trace.Tracer

GetTracer returns a tracer for the given instrumentation scope. Returns a no-op tracer if tracing is not configured.

func (*Config) IsLoggingEnabled

func (c *Config) IsLoggingEnabled() bool

IsLoggingEnabled returns true if OTel logging is configured

func (*Config) IsMetricsEnabled

func (c *Config) IsMetricsEnabled() bool

IsMetricsEnabled returns true if metrics collection is configured

func (*Config) IsTracingEnabled

func (c *Config) IsTracingEnabled() bool

IsTracingEnabled returns true if tracing is configured

func (*Config) Shutdown

func (c *Config) Shutdown(ctx context.Context) error

Shutdown gracefully shuts down all configured providers Call this when your application exits to flush any pending telemetry

func (*Config) WithLoggerProvider

func (c *Config) WithLoggerProvider(lp log.LoggerProvider) *Config

WithLoggerProvider sets a custom LoggerProvider, replacing the default stdout logger

func (*Config) WithMeterProvider

func (c *Config) WithMeterProvider(mp metric.MeterProvider) *Config

WithMeterProvider sets the MeterProvider for metrics collection

func (*Config) WithServiceVersion

func (c *Config) WithServiceVersion(version string) *Config

WithServiceVersion sets the service version for telemetry data

func (*Config) WithTracerProvider

func (c *Config) WithTracerProvider(tp trace.TracerProvider) *Config

WithTracerProvider sets the TracerProvider for distributed tracing

func (*Config) WithoutLogging

func (c *Config) WithoutLogging() *Config

WithoutLogging disables the default logging by setting LoggerProvider to nil

type Field added in v2.2.1

type Field struct {
	Key   string
	Value any
}

Field represents a key-value pair for structured logging. Use the F() function to create fields for type-safe logging.

func F added in v2.2.1

func F(key string, value any) Field

F creates a Field for structured logging. This provides a type-safe, readable way to add context to log messages.

Example:

logger.Info("User logged in", F("user_id", 123), F("email", "user@example.com"))

type LayerContext added in v2.6.0

type LayerContext struct {
	Span   *SpanHelper
	Logger *LogHelper
	// contains filtered or unexported fields
}

LayerContext provides unified access to both span and logger for a layer operation. This combines span tracing and logging with automatic correlation. Base fields are automatically included in Error() and Success() log calls.

func (*LayerContext) Context added in v2.6.0

func (lc *LayerContext) Context() context.Context

Context returns the span's context for passing to child operations.

func (*LayerContext) End added in v2.6.0

func (lc *LayerContext) End()

End finishes the span. Always defer this after creating a LayerContext.

func (*LayerContext) Error added in v2.6.0

func (lc *LayerContext) Error(err error, msg string, fields ...Field) error

Error records an error to both span and logs, then returns the error. Base fields from StartX are automatically included in the log via the Logger. Additional fields are also added as span attributes for correlation.

Example:

if err := repo.Save(lc.Context(), data); err != nil {
    return lc.Error(err, "failed to save", F("id", id))
}

func (*LayerContext) Success added in v2.6.0

func (lc *LayerContext) Success(msg string, fields ...Field)

Success marks the operation as successful in both span and logs. Base fields from StartX are automatically included in the log via the Logger. Additional fields are also added as span attributes for correlation. The message is used as the span status message for consistency with Error().

Example:

lc.Success("User created successfully", F("user_id", userID))

type LayeredSpanHelper added in v2.6.0

type LayeredSpanHelper struct{}

LayeredSpanHelper provides convenience methods for common span patterns across handler, service, and repository layers with consistent naming and attributes. All methods return LayerContext which provides both span and logger for unified tracing and logging with automatic correlation.

func (*LayeredSpanHelper) StartHandler added in v2.6.0

func (l *LayeredSpanHelper) StartHandler(ctx context.Context, component, operation string, fields ...Field) *LayerContext

StartHandler creates a LayerContext for HTTP handler layer operations. Combines span and logger with automatic correlation.

Example:

func (h *EventHandler) Create(c echo.Context) error {
    lc := otel.Layers.StartHandler(c.Request().Context(), "event", "Create",
        F("event.type", eventType))
    defer lc.End()

    lc.Logger.Info("Creating event", F("user_id", userID))
    if err := h.service.Create(lc.Context(), req); err != nil {
        return lc.Error(err, "failed to create event")
    }
    return lc.Success("Event created")
}

func (*LayeredSpanHelper) StartMiddleware added in v2.6.3

func (l *LayeredSpanHelper) StartMiddleware(ctx context.Context, component, operation string, fields ...Field) *LayerContext

StartMiddleware creates a LayerContext for middleware layer operations. Combines span and logger with automatic correlation.

Example:

func AuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        lc := otel.Layers.StartMiddleware(c.Request().Context(), "auth", "ValidateToken",
            F("http.path", c.Path()),
            F("http.method", c.Request().Method))
        defer lc.End()

        lc.Logger.Info("Validating authentication token")
        token := c.Request().Header.Get("Authorization")
        if token == "" {
            return lc.Error(errors.New("missing token"), "authentication failed")
        }

        // Pass updated context to next handler
        c.SetRequest(c.Request().WithContext(lc.Context()))
        if err := next(c); err != nil {
            return lc.Error(err, "request failed")
        }
        return lc.Success("Request processed successfully")
    }
}

func (*LayeredSpanHelper) StartOperations added in v2.6.0

func (l *LayeredSpanHelper) StartOperations(ctx context.Context, component, operation string, fields ...Field) *LayerContext

StartOperations creates a LayerContext for operations layer orchestration. Combines span and logger with automatic correlation.

Example:

func (o *EventOps) ProcessQueue(ctx context.Context, queueName string) error {
    lc := otel.Layers.StartOperations(ctx, "event", "ProcessQueue",
        F("queue.name", queueName))
    defer lc.End()

    lc.Logger.Info("Processing queue")
    if err := o.service.Process(lc.Context()); err != nil {
        return lc.Error(err, "failed to process queue")
    }
    return lc.Success("Queue processed")
}

func (*LayeredSpanHelper) StartRepository added in v2.6.0

func (l *LayeredSpanHelper) StartRepository(ctx context.Context, component, operation string, fields ...Field) *LayerContext

StartRepository creates a LayerContext for repository layer data access. Combines span and logger with automatic correlation.

Example:

func (r *EventRepository) FindByID(ctx context.Context, eventID string) (*Event, error) {
    lc := otel.Layers.StartRepository(ctx, "event", "FindByID",
        F("event.id", eventID),
        F("db.operation", "select"))
    defer lc.End()

    lc.Logger.Debug("Querying database")
    event, err := r.db.QueryRow(lc.Context(), query, eventID)
    if err != nil {
        return nil, lc.Error(err, "query failed")
    }
    lc.Success("Event found")
    return event, nil
}

func (*LayeredSpanHelper) StartService added in v2.6.0

func (l *LayeredSpanHelper) StartService(ctx context.Context, component, operation string, fields ...Field) *LayerContext

StartService creates a LayerContext for service layer business logic. Combines span and logger with automatic correlation.

Example:

func (s *EventService) CancelEvent(ctx context.Context, eventID string) error {
    lc := otel.Layers.StartService(ctx, "event", "CancelEvent",
        F("event.id", eventID))
    defer lc.End()

    lc.Logger.Info("Canceling event")
    if err := s.repo.Update(lc.Context(), data); err != nil {
        return lc.Error(err, "failed to update event")
    }
    return lc.Success("Event cancelled")
}

type LogHelper added in v2.2.0

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

LogHelper provides OTel-aware logging that automatically correlates logs with traces. It uses OTel logging when available (with automatic trace_id/span_id injection), otherwise falls back to plain zerolog.

This is the standard logging pattern for all packages in github.com/jasoet/pkg/v2:

  • When OTel is configured: uses OTel LoggerProvider for automatic log-span correlation
  • When OTel is not configured: falls back to zerolog

Usage:

logger := otel.NewLogHelper(ctx, cfg, "scope-name", "function-name")
logger.Debug("message", "key", "value")
logger.Info("message", "key", "value")
logger.Error(err, "message", "key", "value")

func NewLogHelper added in v2.2.0

func NewLogHelper(ctx context.Context, config *Config, scopeName, function string) *LogHelper

NewLogHelper creates a logger that uses OTel when available, zerolog otherwise. When OTel is enabled, logs are automatically correlated with active spans.

Parameters:

  • ctx: Context for trace correlation
  • config: OTel configuration (can be nil for zerolog-only mode)
  • scopeName: OpenTelemetry scope name (e.g., "github.com/jasoet/pkg/v2/argo")
  • function: Function name to include in logs (optional, can be empty string)

Example:

// With OTel configured and function name
logger := otel.NewLogHelper(ctx, otelConfig, "github.com/jasoet/pkg/v2/mypackage", "mypackage.DoWork")
logger.Debug("Starting work", F("workerId", 123))

// Without function name (when used with spans)
logger := otel.NewLogHelper(ctx, otelConfig, "service.user", "")
logger.Info("Work completed")

// Without OTel (falls back to zerolog)
logger := otel.NewLogHelper(ctx, nil, "", "mypackage.DoWork")
logger.Info("Work completed")

func (*LogHelper) Debug added in v2.2.0

func (h *LogHelper) Debug(msg string, fields ...Field)

Debug logs a debug-level message with optional fields. If OTel is enabled, automatically adds trace_id and span_id.

Example:

logger.Debug("Processing request", F("request_id", reqID), F("user", userID))

func (*LogHelper) Error added in v2.2.0

func (h *LogHelper) Error(err error, msg string, fields ...Field)

Error logs an error-level message with optional fields. Also sets span status to error if a span is active.

Example:

logger.Error(err, "Failed to process request", F("request_id", reqID), F("attempt", 3))

func (*LogHelper) Info added in v2.2.0

func (h *LogHelper) Info(msg string, fields ...Field)

Info logs an info-level message with optional fields. If OTel is enabled, automatically adds trace_id and span_id.

Example:

logger.Info("User logged in", F("user_id", 123), F("role", "admin"))

func (*LogHelper) Span added in v2.6.0

func (h *LogHelper) Span() trace.Span

Span returns the active span from the logger's context. Returns a non-nil span even if no span is active (use span.IsRecording() to check).

Example:

span := logger.Span()
if span.IsRecording() {
    span.AddEvent("custom.event")
}

func (*LogHelper) Warn added in v2.2.0

func (h *LogHelper) Warn(msg string, fields ...Field)

Warn logs a warning-level message with optional fields. If OTel is enabled, automatically adds trace_id and span_id.

Example:

logger.Warn("Rate limit approaching", F("current", 95), F("limit", 100))

func (*LogHelper) WithFields added in v2.7.0

func (h *LogHelper) WithFields(fields ...Field) *LogHelper

WithFields returns a new LogHelper with additional base fields. These fields will be automatically included in every log call.

Example:

logger := otel.NewLogHelper(ctx, cfg, "service.user", "").
    WithFields(F("user.id", userID), F("action", "create"))
logger.Info("Processing request") // Includes user.id and action

type LogLevel added in v2.4.9

type LogLevel = logging.LogLevel

LogLevel is an alias for logging.LogLevel for convenience. Use logging.LogLevel constants directly (logging.LogLevelDebug, etc.)

type LoggerProviderOption added in v2.4.8

type LoggerProviderOption func(*loggerProviderConfig)

LoggerProviderOption configures LoggerProvider behavior

func WithConsoleOutput added in v2.4.8

func WithConsoleOutput(enabled bool) LoggerProviderOption

WithConsoleOutput enables console logging alongside OTLP

func WithLogLevel added in v2.4.9

func WithLogLevel(level LogLevel) LoggerProviderOption

WithLogLevel sets the log level for console output Valid levels: "debug", "info", "warn", "error", "none" If not specified, defaults to "info"

func WithOTLPEndpoint added in v2.4.8

func WithOTLPEndpoint(endpoint string, insecure bool) LoggerProviderOption

WithOTLPEndpoint enables OTLP log export

type SpanHelper added in v2.6.0

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

SpanHelper provides a convenient way to create and manage spans with automatic error handling and status management. It's designed for use in service and repository layers where consistent span instrumentation is needed.

Usage pattern:

func (s *Service) DoWork(ctx context.Context, id string) error {
    span := otel.StartSpan(ctx, "service.example", "Service.DoWork",
        otel.WithAttribute("entity.id", id))
    defer span.End()

    // IMPORTANT: Use span.Context() for child operations to maintain trace correlation
    if err := s.repository.Save(span.Context(), data); err != nil {
        return span.Error(err, "failed to save data")
    }

    return span.Success()
}

func StartSpan added in v2.6.0

func StartSpan(ctx context.Context, tracerName, operationName string, opts ...SpanOption) *SpanHelper

StartSpan creates a new span with the given tracer name and operation name. The tracer name should follow the pattern "layer.component" (e.g., "service.event", "repository.ticket"). The operation name should be descriptive (e.g., "EventService.CancelEvent", "TicketRepository.FindByID").

This is the recommended way to create spans in service and repository layers.

Example:

// In service layer
span := otel.StartSpan(ctx, "service.event", "EventService.CancelEvent",
    otel.WithAttribute("event.id", eventID))
defer span.End()

// In repository layer
span := otel.StartSpan(ctx, "repository.event", "EventRepository.FindByID",
    otel.WithAttribute("event.id", eventID),
    otel.WithAttribute("db.operation", "select"))
defer span.End()

func (*SpanHelper) AddAttribute added in v2.6.0

func (h *SpanHelper) AddAttribute(key string, value any) *SpanHelper

AddAttribute adds a single attribute to the span.

func (*SpanHelper) AddAttributes added in v2.6.0

func (h *SpanHelper) AddAttributes(fields ...Field) *SpanHelper

AddAttributes adds multiple attributes to the span.

func (*SpanHelper) AddEvent added in v2.6.0

func (h *SpanHelper) AddEvent(name string, fields ...Field) *SpanHelper

AddEvent adds an event to the span with optional attributes.

Example:

span.AddEvent("cache.hit", F("key", cacheKey), F("ttl", ttl))

func (*SpanHelper) Context added in v2.6.0

func (h *SpanHelper) Context() context.Context

Context returns the context with the span attached. Use this when calling child functions that need the span context.

func (*SpanHelper) End added in v2.6.0

func (h *SpanHelper) End()

End finishes the span. Always defer this after creating a span.

Example:

span := otel.StartSpan(ctx, "service", "Operation")
defer span.End()

func (*SpanHelper) Error added in v2.6.0

func (h *SpanHelper) Error(err error, message string) error

Error records an error and sets the span status to error. Returns the error unchanged for easy error propagation.

Example:

if err := doWork(); err != nil {
    return span.Error(err, "work failed")
}

func (*SpanHelper) FunctionLogger added in v2.6.1

func (h *SpanHelper) FunctionLogger(scopeName string, function string) *LogHelper

FunctionLogger creates a LogHelper that is automatically correlated with this span. Returns a LogHelper with the default zerolog logger if no config is stored in the context. Use ContextWithConfig() to store config in context before creating spans.

Example:

ctx = otel.ContextWithConfig(ctx, cfg)
span := otel.StartSpan(ctx, "service.user", "CreateUser",
    otel.WithAttribute("user.id", userID))
defer span.End()

logger := span.FunctionLogger("service.user","function.name")
logger.Info("Creating user", F("email", email))

func (*SpanHelper) LogEvent added in v2.6.0

func (h *SpanHelper) LogEvent(logger *LogHelper, eventName string, fields ...Field) *SpanHelper

LogEvent creates both a span event and a log entry for better correlation. This is useful for significant events that should appear in both traces and logs.

Example:

logger := span.Logger("service.cache")
span.LogEvent(logger, "cache.miss",
    F("key", cacheKey),
    F("reason", "expired"))

func (*SpanHelper) Logger added in v2.6.0

func (h *SpanHelper) Logger(scopeName string) *LogHelper

Logger creates a LogHelper that is automatically correlated with this span. Returns a LogHelper with the default zerolog logger if no config is stored in the context. Use ContextWithConfig() to store config in context before creating spans.

Example:

ctx = otel.ContextWithConfig(ctx, cfg)
span := otel.StartSpan(ctx, "service.user", "CreateUser",
    otel.WithAttribute("user.id", userID))
defer span.End()

logger := span.Logger("service.user")

logger.Info("Creating user", F("email", email))

func (*SpanHelper) Span added in v2.6.0

func (h *SpanHelper) Span() trace.Span

Span returns the underlying trace.Span for advanced usage.

func (*SpanHelper) Success added in v2.6.0

func (h *SpanHelper) Success(message string)

Success marks the span as successful with an optional message. This is optional but provides explicit success signaling.

Example:

span.Success("operation completed")

type SpanOption added in v2.6.0

type SpanOption func(*spanConfig)

SpanOption allows customizing span creation

func WithAttribute added in v2.6.0

func WithAttribute(key string, value any) SpanOption

WithAttribute adds an attribute to the span

func WithAttributes added in v2.6.0

func WithAttributes(fields ...Field) SpanOption

WithAttributes adds multiple attributes to the span

func WithSpanKind added in v2.6.0

func WithSpanKind(kind trace.SpanKind) SpanOption

WithSpanKind sets the span kind (Internal, Server, Client, Producer, Consumer)

Jump to

Keyboard shortcuts

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