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 (spans only, no logging)
simpleApp := otel.Layers.StartService(ctx, "simple", "DoWork")
defer simpleApp.End()
// simpleApp.Logger is nil
// 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()
if productionApp.Logger != nil {
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 (spans only, no logging)
lc1 := otel.Layers.StartService(ctx, "user", "CreateUser")
defer lc1.End()
// lc1.Logger is nil
// 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()
if lc2.Logger != nil {
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
fmt.Println("=== Example 1: LayerContext ===")
lc := otel.Layers.StartService(ctx, "user", "CreateUser",
otel.F("user.id", "12345"))
defer lc.End()
if lc.Logger != nil {
lc.Logger.Info("Creating user", otel.F("email", "user@example.com"))
}
// Simulate success
_ = lc.Success("User created successfully", otel.F("user.id", "12345"))
// 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 := span.Logger("service.order")
if logger != nil {
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)
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()
if middlewareCtx.Logger != nil {
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()
if handlerCtx.Logger != nil {
handlerCtx.Logger.Info("Handling request")
}
// Operations layer (config available from handler.Context())
opsCtx := otel.Layers.StartOperations(handlerCtx.Context(), "user", "ProcessUserRequest")
defer opsCtx.End()
if opsCtx.Logger != nil {
opsCtx.Logger.Info("Orchestrating user request")
}
// Service layer
serviceCtx := otel.Layers.StartService(opsCtx.Context(), "user", "GetUser",
otel.F("user.id", "123"))
defer serviceCtx.End()
if serviceCtx.Logger != nil {
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()
if repoCtx.Logger != nil {
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.
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
handler := otel.Layers.StartHandler(ctx, "user", "CreateUser",
otel.F("http.method", "POST"),
otel.F("http.path", "/users"))
defer handler.End()
if handler.Logger != nil {
handler.Logger.Info("HTTP request received")
}
// Operations layer - config automatically available from context
ops := otel.Layers.StartOperations(handler.Context(), "user", "CreateUserOperation")
defer ops.End()
if ops.Logger != nil {
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()
if service.Logger != nil {
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()
if repo.Logger != nil {
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
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()
if authCtx.Logger != nil {
authCtx.Logger.Info("Checking authorization header")
}
// Simulate successful auth
_ = 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()
if corsCtx.Logger != nil {
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()
if rateLimitCtx.Logger != nil {
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()
if mw1Ctx.Logger != nil {
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()
if mw2Ctx.Logger != nil {
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)
lc := otel.Layers.StartService(ctx, "user", "CreateUser",
otel.F("user.id", "12345"))
defer lc.End()
// Logs will include trace_id and span_id automatically when spans are active
if lc.Logger != nil {
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. Useful for simple apps that only need basic tracing without logging.
package main
import (
"context"
"errors"
"fmt"
"github.com/jasoet/pkg/v2/otel"
)
func main() {
ctx := context.Background()
// No config in context - spans work, but logger is nil
lc := otel.Layers.StartService(ctx, "user", "CreateUser",
otel.F("user.id", "12345"))
defer lc.End()
// lc.Logger is nil - only spans are created
if lc.Logger == nil {
fmt.Println("Logger is nil without config")
}
// Span tracking still works
lc.Span.AddAttribute("status", "processing")
// Error handling works (recorded in span)
err := errors.New("validation failed")
if err != nil {
_ = lc.Error(err, "User creation failed")
return
}
_ = lc.Success("User created successfully")
fmt.Println("Spans work without OTel config")
}
Index ¶
- Variables
- func ContextWithConfig(ctx context.Context, cfg *Config) context.Context
- func NewLoggerProviderWithOptions(serviceName string, debug bool, 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) Handler(ctx context.Context, component, operation string, fields ...Field) *SpanHelper
- func (l *LayeredSpanHelper) Middleware(ctx context.Context, component, operation string, fields ...Field) *SpanHelper
- func (l *LayeredSpanHelper) Operations(ctx context.Context, component, operation string, fields ...Field) *SpanHelper
- func (l *LayeredSpanHelper) Repository(ctx context.Context, component, operation string, fields ...Field) *SpanHelper
- func (l *LayeredSpanHelper) Service(ctx context.Context, component, operation string, fields ...Field) *SpanHelper
- 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
- 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() error
- 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, debug bool, 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
- debug: If true, sets log level to Debug, otherwise Info
- 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", false,
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
}
LayerContext provides unified access to both span and logger for a layer operation. This combines span tracing and logging with automatic correlation.
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.
Example:
if err := repo.Save(lc.Context(), data); err != nil {
return lc.Error(err, "failed to save", F("id", id))
}
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.
func (*LayeredSpanHelper) Handler ¶ added in v2.6.0
func (l *LayeredSpanHelper) Handler(ctx context.Context, component, operation string, fields ...Field) *SpanHelper
Handler creates a span for HTTP handler layer operations. Automatically adds handler-specific attributes.
Example:
func (h *EventHandler) Create(c echo.Context) error {
ctx := c.Request().Context()
span := otel.Layers.Handler(ctx, "admin.event", "Create",
F("event.id", req.EventID))
defer span.End()
// ... handler logic
}
func (*LayeredSpanHelper) Middleware ¶ added in v2.6.3
func (l *LayeredSpanHelper) Middleware(ctx context.Context, component, operation string, fields ...Field) *SpanHelper
Middleware creates a span for middleware layer operations. This layer is used for HTTP middleware that intercepts requests/responses, authentication, authorization, rate limiting, and other cross-cutting concerns. Automatically adds middleware-specific attributes.
Example:
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
span := otel.Layers.Middleware(ctx, "auth", "ValidateToken",
F("http.path", r.URL.Path))
defer span.End()
// ... middleware logic
next.ServeHTTP(w, r.WithContext(span.Context()))
})
}
func (*LayeredSpanHelper) Operations ¶ added in v2.6.0
func (l *LayeredSpanHelper) Operations(ctx context.Context, component, operation string, fields ...Field) *SpanHelper
Operations creates a span for operations layer that wraps final functionality. This layer is typically used for CLI commands, scheduled jobs, or orchestration logic that coordinates services without initialization concerns. Automatically adds operations-specific attributes.
Example:
func (o *EventOps) ProcessEventQueue(ctx context.Context, queueName string) error {
span := otel.Layers.Operations(ctx, "event", "ProcessEventQueue",
F("queue.name", queueName))
defer span.End()
// ... operations logic coordinating services
}
func (*LayeredSpanHelper) Repository ¶ added in v2.6.0
func (l *LayeredSpanHelper) Repository(ctx context.Context, component, operation string, fields ...Field) *SpanHelper
Repository creates a span for repository layer data access. Automatically adds repository and database-specific attributes.
Example:
func (r *EventRepository) FindByID(ctx context.Context, eventID string) (*Event, error) {
span := otel.Layers.Repository(ctx, "event", "FindByID",
F("event.id", eventID),
F("db.operation", "select"))
defer span.End()
// ... repository logic
}
func (*LayeredSpanHelper) Service ¶ added in v2.6.0
func (l *LayeredSpanHelper) Service(ctx context.Context, component, operation string, fields ...Field) *SpanHelper
Service creates a span for service layer business logic. Automatically adds service-specific attributes.
Example:
func (s *EventService) CancelEvent(ctx context.Context, eventID string) error {
span := otel.Layers.Service(ctx, "event", "CancelEvent",
F("event.id", eventID))
defer span.End()
// ... service logic
}
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"))
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" (or "debug" if debug parameter is true)
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()
if err := s.repository.Save(ctx, 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() error
Success marks the span as successful and returns nil. This is optional but provides explicit success signaling.
Example:
return span.Success()
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)