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 ¶
- Variables
- func ContextWithConfig(ctx context.Context, cfg *Config) context.Context
- func NewLoggerProviderWithOptions(serviceName string, opts ...LoggerProviderOption) (log.LoggerProvider, error)
- type Config
- func (c *Config) DisableLogging() *Config
- func (c *Config) DisableMetrics() *Config
- func (c *Config) DisableTracing() *Config
- func (c *Config) GetLogger(scopeName string, opts ...log.LoggerOption) log.Logger
- func (c *Config) GetMeter(scopeName string, opts ...metric.MeterOption) metric.Meter
- func (c *Config) GetTracer(scopeName string, opts ...trace.TracerOption) trace.Tracer
- func (c *Config) IsLoggingEnabled() bool
- func (c *Config) IsMetricsEnabled() bool
- func (c *Config) IsTracingEnabled() bool
- func (c *Config) Shutdown(ctx context.Context) error
- func (c *Config) WithLoggerProvider(lp log.LoggerProvider) *Config
- func (c *Config) WithMeterProvider(mp metric.MeterProvider) *Config
- func (c *Config) WithServiceVersion(version string) *Config
- func (c *Config) WithTracerProvider(tp trace.TracerProvider) *Config
- func (c *Config) WithoutLogging() *Config
- type Field
- type LayerContext
- type LayeredSpanHelper
- func (l *LayeredSpanHelper) StartHandler(ctx context.Context, component, operation string, fields ...Field) *LayerContext
- func (l *LayeredSpanHelper) StartMiddleware(ctx context.Context, component, operation string, fields ...Field) *LayerContext
- func (l *LayeredSpanHelper) StartOperations(ctx context.Context, component, operation string, fields ...Field) *LayerContext
- func (l *LayeredSpanHelper) StartRepository(ctx context.Context, component, operation string, fields ...Field) *LayerContext
- func (l *LayeredSpanHelper) StartService(ctx context.Context, component, operation string, fields ...Field) *LayerContext
- type LogHelper
- func (h *LogHelper) Debug(msg string, fields ...Field)
- func (h *LogHelper) Error(err error, msg string, fields ...Field)
- func (h *LogHelper) Info(msg string, fields ...Field)
- func (h *LogHelper) Span() trace.Span
- func (h *LogHelper) Warn(msg string, fields ...Field)
- func (h *LogHelper) WithFields(fields ...Field) *LogHelper
- type LogLevel
- type LoggerProviderOption
- type SpanHelper
- func (h *SpanHelper) AddAttribute(key string, value any) *SpanHelper
- func (h *SpanHelper) AddAttributes(fields ...Field) *SpanHelper
- func (h *SpanHelper) AddEvent(name string, fields ...Field) *SpanHelper
- func (h *SpanHelper) Context() context.Context
- func (h *SpanHelper) End()
- func (h *SpanHelper) Error(err error, message string) error
- func (h *SpanHelper) FunctionLogger(scopeName string, function string) *LogHelper
- func (h *SpanHelper) LogEvent(logger *LogHelper, eventName string, fields ...Field) *SpanHelper
- func (h *SpanHelper) Logger(scopeName string) *LogHelper
- func (h *SpanHelper) Span() trace.Span
- func (h *SpanHelper) Success(message string)
- type SpanOption
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var Layers = &LayeredSpanHelper{}
Layers provides convenience methods for creating layer-specific spans
Functions ¶
func ContextWithConfig ¶ added in v2.6.0
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
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 ¶
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
DisableLogging disables logging by setting LoggerProvider to nil
func (*Config) DisableMetrics ¶ added in v2.6.2
DisableMetrics disables metrics by setting MeterProvider to nil
func (*Config) DisableTracing ¶ added in v2.6.2
DisableTracing disables tracing by setting TracerProvider to nil
func (*Config) GetLogger ¶
GetLogger returns a logger for the given instrumentation scope. Returns a no-op logger if logging is not configured.
func (*Config) GetMeter ¶
GetMeter returns a meter for the given instrumentation scope. Returns a no-op meter if metrics are not configured.
func (*Config) GetTracer ¶
GetTracer returns a tracer for the given instrumentation scope. Returns a no-op tracer if tracing is not configured.
func (*Config) IsLoggingEnabled ¶
IsLoggingEnabled returns true if OTel logging is configured
func (*Config) IsMetricsEnabled ¶
IsMetricsEnabled returns true if metrics collection is configured
func (*Config) IsTracingEnabled ¶
IsTracingEnabled returns true if tracing is configured
func (*Config) Shutdown ¶
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 ¶
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 ¶
WithoutLogging disables the default logging by setting LoggerProvider to nil
type Field ¶ added in v2.2.1
Field represents a key-value pair for structured logging. Use the F() function to create fields for type-safe logging.
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
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
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
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
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
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
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
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
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)