xlog

package module
v0.5.1 Latest Latest
Warning

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

Go to latest
Published: May 8, 2026 License: MIT Imports: 12 Imported by: 3

README

xlog

CI Go Report Card Coverage Go Reference

xlog is a Go logging toolkit for context-aware structured logging. It stores loggers in context, falls back to a global logger, supports zap and slog adapters, and includes OpenTelemetry tracing helpers.

Features

  • Context-aware logging - logger is extracted from context
  • Global logger fallback support with thread-safe replacement
  • Structured logging via field package - clear separation between logging and field constructors
  • Backend-agnostic field types - works with Zap, slog, or custom loggers
  • Printf-style formatting (Debugf, Infof, etc.)
  • All logging levels: Debug, Info, Warn, Error, Fatal, Panic
  • Zero allocation when logger is not in context (uses noop adapter)
  • OpenTelemetry integration - span management with automatic logger enrichment
  • Distributed tracing support with span creation, events, and attributes

Installation

go get github.com/ruko1202/xlog

Quick Start

For a complete working example, see example/app.

Basic Usage
package main

import (
	"context"
	"github.com/ruko1202/xlog"
	"github.com/ruko1202/xlog/xfield"
	"go.uber.org/zap"
)

const (
	appName = "xlogApp"
)

func main() {
	// Create logger and wrap it in adapter
	logger, _ := zap.NewProduction()
	defer logger.Sync()

	// Add logger to context
	ctx := context.Background()
	ctx = xlog.ContextWithLogger(ctx, xlog.NewZapAdapter(logger))

	// Use logging
	xlog.Infof(ctx, "application `%s` started", appName)
	xlog.Debug(ctx, "debug information", field.String("version", "1.0.0"))
}
Using Global Logger

It is convenient if you have background jobs that carry their own context.Context

package main

import (
    "context"
    "github.com/ruko1202/xlog"
    "go.uber.org/zap"
)

func main() {
    // Replace global logger using zap's native function
    logger, _ := zap.NewDevelopment()
    zap.ReplaceGlobals(logger)

    // Use without adding logger to context
    ctx := context.Background()
    xlog.Info(ctx, "using global logger")
}

API

Context Functions
ContextWithLogger(ctx context.Context, logger *zap.Logger) context.Context

Adds a logger to the context.

ctx := xlog.ContextWithLogger(context.Background(), logger)
LoggerFromContext(ctx context.Context) *zap.Logger

Extracts logger from context. If logger is not found, returns the global logger.

logger := xlog.LoggerFromContext(ctx)
logger.Info("direct zap usage")
Global Logger Management

Use zap's native ReplaceGlobals() function to replace the global logger. This is useful for testing or temporarily changing the logger.

logger, _ := zap.NewProduction()
undo := zap.ReplaceGlobals(logger)
defer undo() // Restore previous logger when done
WithOperation(ctx context.Context, operation string, fields ...zap.Field) context.Context

Creates a new context with a named logger for a specific operation.

ctx = xlog.WithOperation(ctx, "payment-processing",
    zap.String("user_id", "12345"),
    zap.String("payment_id", "pay_xyz"),
)
xlog.Info(ctx, "processing payment")

⚠️ Performance Warning: This function has performance overhead due to creating new logger instances (~4μs and allocations per call).

Recommended alternative - use WithFields instead:

// Instead of WithOperation - use WithFields for better performance
ctx = xlog.WithFields(ctx,
    zap.String("operation", "payment-processing"),
    zap.String("user_id", "12345"),
    zap.String("payment_id", "pay_xyz"),
)
xlog.Info(ctx, "processing payment")

Or use fields directly in log calls for single statements:

xlog.Info(ctx, "processing payment",
    zap.String("operation", "payment-processing"),
    zap.String("user_id", "12345"),
    zap.String("payment_id", "pay_xyz"),
)

Use WithOperation only when:

  • You need the operation name in the logger namespace (affects log structure)
  • Performance is not critical
  • The operation context will be used for many log statements
WithFields(ctx context.Context, fields ...zap.Field) context.Context

Creates a new context with additional fields added to the logger. This is the recommended way to add persistent fields to a logger context.

// Add user context to logger
ctx = xlog.WithFields(ctx,
    zap.String("user_id", "12345"),
    zap.String("session_id", "sess_xyz"),
)

// All subsequent logs will include these fields
xlog.Info(ctx, "user action performed")
xlog.Debug(ctx, "processing request")

Performance: Better than WithOperation but still creates new logger instances. For single log statements, passing fields directly is most efficient.

Span Management Functions

xlog provides integration with OpenTelemetry for distributed tracing. These functions help manage spans alongside logging.

ReplaceTracerName(name string) func()

Sets the global tracer name for creating spans and returns a function to restore the previous name. Call this once during application initialization. This function is thread-safe.

restore := xlog.ReplaceTracerName("my-service")
defer restore() // Restore previous tracer name when done
ContextWithTracer(ctx context.Context, tracer trace.Tracer) context.Context

Adds a tracer to the context. If tracer is nil, the global tracer is used.

tracer := otel.GetTracerProvider().Tracer("my-service")
ctx = xlog.ContextWithTracer(ctx, tracer)
TracerFromContext(ctx context.Context) trace.Tracer

Extracts tracer from context. If no tracer is found, returns the global tracer.

tracer := xlog.TracerFromContext(ctx)
ctx, span := tracer.Start(ctx, "my-operation")
defer span.End()
SpanFromContext(ctx context.Context) trace.Span

Extracts the current span from context. If no span is found, returns a NoopSpan (safe to use).

span := xlog.SpanFromContext(ctx)
span.SetAttributes(attribute.String("key", "value"))
WithOperationSpan(ctx context.Context, operation string, fields ...zap.Field) (context.Context, trace.Span)

Creates a new span for an operation and adds it to the context along with an enriched logger. The logger automatically includes the operation name and any additional fields.

ctx, span := xlog.WithOperationSpan(ctx, "process-payment",
    zap.String("user_id", "12345"),
    zap.String("payment_id", "pay_xyz"),
)
defer span.End()

xlog.Info(ctx, "processing payment") // Includes operation and fields

Returns:

  • New context with span and enriched logger
  • Span instance that should be ended with defer span.End()

Usage pattern:

func handleRequest(ctx context.Context) error {
    ctx, span := xlog.WithOperationSpan(ctx, "handleRequest")
    defer span.End()

    // All logs in this context will include the operation name
    xlog.Info(ctx, "request started")

    // ...

    return nil
}
AddSpanEvent(ctx context.Context, message string)

Adds an event to the current span (if present in context). Useful for marking important moments in trace execution.

xlog.AddSpanEvent(ctx, "database query started")
// ... perform database query ...
xlog.AddSpanEvent(ctx, "database query completed")
SetSpanAttributes(ctx context.Context, attrs ...attribute.KeyValue)

Sets attributes on the current span (if present in context). Use OpenTelemetry attribute types.

import "go.opentelemetry.io/otel/attribute"

xlog.SetSpanAttributes(ctx,
    attribute.String("user.id", "12345"),
    attribute.Int("user.age", 25),
    attribute.Bool("user.premium", true),
)
RecordSpanError(ctx context.Context, err error, options ...trace.EventOption)

Records an error on the current span and sets its status to Error. Safe to call even when no span is present in context.

if err := doSomething(); err != nil {
    xlog.RecordSpanError(ctx, err, trace.WithStackTrace(true))
    return err
}

Note: All span functions work safely even when no span is present in context (no-op behavior).

Logging Functions

All logging functions require context.Context as the first argument.

Structured Logging

Functions with structured fields (zap.Field):

  • Debug(ctx context.Context, msg string, fields ...zap.Field)
  • Info(ctx context.Context, msg string, fields ...zap.Field)
  • Warn(ctx context.Context, msg string, fields ...zap.Field)
  • Error(ctx context.Context, msg string, fields ...zap.Field)
  • Fatal(ctx context.Context, msg string, fields ...zap.Field)
  • Panic(ctx context.Context, msg string, fields ...zap.Field)
xlog.Info(ctx, "user logged in",
    zap.String("user_id", "12345"),
    zap.String("ip", "192.168.1.1"),
    zap.Duration("login_time", time.Second*2),
)
Printf-style Logging

Functions with string formatting:

  • Debugf(ctx context.Context, template string, args ...any)
  • Infof(ctx context.Context, template string, args ...any)
  • Warnf(ctx context.Context, template string, args ...any)
  • Errorf(ctx context.Context, template string, args ...any)
  • Fatalf(ctx context.Context, template string, args ...any)
  • Panicf(ctx context.Context, template string, args ...any)
userID := "12345"
xlog.Infof(ctx, "user %s logged in", userID)
xlog.Errorf(ctx, "request processing error: code %d", 500)

Complete Example

The example/app directory contains a complete working application demonstrating xlog integration with OpenTelemetry, distributed tracing, and metrics:

Structure:

  • main.go - Application setup, Echo server, OpenTelemetry initialization
  • http_handler.go - HTTP handlers with span creation and logging
  • otel.go - OpenTelemetry configuration (traces via gRPC, metrics via Prometheus)
  • worker.go - Background worker simulating HTTP requests
  • compose.yml - Docker Compose setup with Jaeger, Prometheus, and Grafana
  • otel-collector-config.yaml - OpenTelemetry Collector configuration

Features demonstrated:

  • Context-aware logging with automatic trace ID injection
  • Integration with Echo web framework
  • OpenTelemetry spans with xlog.WithOperationSpan()
  • Distributed tracing with Jaeger
  • Prometheus metrics collection
  • Trace ID propagation in HTTP headers (X-Request-ID)
  • Background workers with proper context handling

Running the example:

cd example

# Start infrastructure (Jaeger, Prometheus, Grafana, OTel Collector)
docker compose up -d

# Run the application
go run app/*.go

# Test the API
curl http://localhost:8080/api/work?user_id=123
curl http://localhost:8080/api/work?user_id=456&fail=true

# View traces: http://localhost:16686 (Jaeger UI)
# View metrics: http://localhost:9090 (Prometheus)
# View dashboards: http://localhost:3000 (Grafana, admin/admin)

Usage Examples

HTTP Handler

Recommended approach using WithFields:

func handleRequest(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()

    // Add request context using WithFields (performance-friendly)
    ctx = xlog.WithFields(ctx,
        zap.String("request_id", uuid.NewString()),
        zap.String("method", r.Method),
        zap.String("path", r.URL.Path),
        zap.String("query", r.URL.RawQuery),
    )

    xlog.Info(ctx, "request processing started")

    if err := processRequest(ctx, r); err != nil {
        xlog.Error(ctx, "request processing error", zap.Error(err))
        http.Error(w, "Internal Server Error", 500)
        return
    }

    xlog.Info(ctx, "request successfully processed")
}

func processRequest(ctx context.Context, r *http.Request) error {
    // For single log - pass fields directly (most efficient)
    userID := r.URL.Query().Get("userId")
    xlog.Debug(ctx, "processing user request",
        zap.String("operation", "process-request"),
        zap.String("user_id", userID),
    )

    // ... business logic ...

    return nil
}

Alternative (with operation namespace):

If you need the operation name in logger namespace (which affects log structure):

func handleRequest(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    ctx = xlog.WithOperation(ctx, "handleRequest",
        zap.String("request_id", uuid.NewString()),
        zap.String("method", r.Method),
        zap.String("path", r.URL.Path),
    )

    xlog.Info(ctx, "request processing started")
    // ...
}

With distributed tracing:

For complete observability with OpenTelemetry spans:

func handleRequest(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    ctx, span := xlog.WithOperationSpan(ctx, "handleRequest",
        zap.String("method", r.Method),
        zap.String("path", r.URL.Path),
    )
    defer span.End()

    xlog.Info(ctx, "request processing started")
    // Logs will include trace_id and span_id automatically
    // ...
}

See example/app for a complete working example with Echo framework and OpenTelemetry.

Database Operations
func getUserByID(ctx context.Context, userID string) (*User, error) {
    xlog.Debug(ctx, "fetching user from database", zap.String("user_id", userID))

    user, err := db.Query(ctx, "SELECT * FROM users WHERE id = ?", userID)
    if err != nil {
        xlog.Error(ctx, "database query error",
            zap.String("user_id", userID),
            zap.Error(err),
        )
        return nil, err
    }

    xlog.Info(ctx, "user found", zap.String("user_id", userID))
    return user, nil
}
Background Tasks
func backgroundWorker(ctx context.Context) {
    // Add worker context using WithFields
    ctx = xlog.WithFields(ctx,
        zap.String("worker", "background-processor"),
    )

    xlog.Info(ctx, "starting background processor")

    for {
        select {
        case <-ctx.Done():
            xlog.Info(ctx, "stopping processor")
            return
        case task := <-taskQueue:
            // Performance-friendly: pass fields directly for one-off logs
            xlog.Debug(ctx, "processing task",
                zap.String("task_id", task.ID),
            )
            if err := processTask(ctx, task); err != nil {
                xlog.Error(ctx, "task processing error",
                    zap.String("task_id", task.ID),
                    zap.Error(err),
                )
            }
        }
    }
}

Logging Levels

  • Debug - Detailed debug information
  • Info - Informational messages about normal operation
  • Warn - Warnings about potential issues
  • Error - Errors that need to be handled
  • Fatal - Critical errors that terminate the application (calls os.Exit(1))
  • Panic - Critical errors that cause panic

Development

Testing

The package includes a comprehensive test suite. To run tests:

# Run all tests
make tloc

# Run tests with coverage
make test-cov

Or using go commands directly:

# Run tests
go test -v ./...

# Run tests with coverage
go test -cover ./...
Code Quality
# Install linter
make bin-deps

# Run linter
make lint

# Format code
make fmt
CI/CD

The project uses GitHub Actions for continuous integration:

  • CI Pipeline - Runs on every push and pull request:

    • Linting with golangci-lint v2.3.0
    • Tests on Go 1.23 and 1.24 with race detector
    • Coverage reporting in pull requests
    • Automatic coverage badge updates on main branch
  • Release Pipeline - Triggered on version tags (v*):

    • Automated releases using GoReleaser
    • Changelog generation

See .github/workflows/ for workflow configurations.

Performance

xlog provides flexible logger abstraction through adapters (ZapAdapter, SlogAdapter), allowing you to switch between different logging backends while maintaining a consistent API.

📊 Benchmarking: See BENCHMARKING.md for detailed performance guidelines and baseline metrics.

Performance Characteristics

Adapter Overhead: The adapter layer introduces minimal overhead:

  • Basic logging: ~2ns/op, 0 allocations
  • Context reuse: ~2ns/op, 0 allocations (13% faster than baseline!)
  • Context creation in loop: +2-3 allocations per call (avoid this pattern)

Key Performance Metrics:

  • Logging with fields directly: ~2ns, 0 allocs
  • WithFields (reused context): ~2ns, 0 allocs
  • WithOperation (reused context): ~2ns, 0 allocs
  • WithOperationSpan: ~670ns, 7 allocs
Performance Best Practices

Follow these patterns to achieve optimal performance with zero allocations in hot paths:

1. ✅ CRITICAL: Always Reuse Context

The most important performance rule. Creating context in a loop causes significant overhead:

// ❌ ANTI-PATTERN: Creates 4-7 allocations per iteration!
for i := 0; i < 1000; i++ {
    ctx := xlog.WithOperation(baseCtx, "process-item")  // 4 allocs, 64ns
    processItem(ctx, items[i])
}

// ✅ BEST PRACTICE: 0 allocations, 32x faster
ctx := xlog.WithOperation(baseCtx, "process-batch")  // One-time cost
for i := 0; i < 1000; i++ {
    processItem(ctx, items[i])  // 0 allocs, 2ns
}

Impact: Reusing context is 32x faster and produces zero allocations vs creating in loop.

2. ✅ Pass Fields Directly for Single Logs

For one-off log statements, pass fields directly (most efficient):

// ✅ BEST: 0 allocations
xlog.Info(ctx, "processing order",
    field.String("order_id", orderID),
    field.String("status", "pending"),
)
3. ✅ Use WithFields for Multiple Logs

When logging multiple times with the same fields, create enriched context once:

// ✅ GOOD: Create context once, reuse many times
ctx = xlog.WithFields(ctx,
    field.String("user_id", userID),
    field.String("session_id", sessionID),
)

// All subsequent logs include these fields with 0 allocations
xlog.Info(ctx, "user authenticated")  // 0 allocs
xlog.Debug(ctx, "loading preferences")  // 0 allocs
xlog.Info(ctx, "session started")  // 0 allocs
4. ⚠️ Use WithOperation Sparingly

WithOperation creates a named logger (affects log namespace). Use only when needed:

// ✅ WHEN TO USE: You need operation in logger namespace
ctx = xlog.WithOperation(ctx, "payment-processor",
    field.String("user_id", userID),
)

// ⚠️ ALTERNATIVE: Use WithFields + operation field (same performance)
ctx = xlog.WithFields(ctx,
    field.String("operation", "payment-processor"),
    field.String("user_id", userID),
)
5. ✅ Appropriate Span Granularity

Spans have moderate cost (~670ns, 7 allocs). Create them at appropriate granularity:

// ❌ BAD: Span per row (thousands of allocations)
for row := range rows {
    ctx, span := xlog.WithOperationSpan(ctx, "validate-row")
    validate(row)
    span.End()
}

// ✅ GOOD: One span for entire batch
ctx, span := xlog.WithOperationSpan(ctx, "validate-batch")
defer span.End()
for row := range rows {
    validate(row)  // 0 allocations
}
6. ✅ Prefer Structured Logging

Structured fields are more efficient and enable better log analysis:

// ⚠️ Less efficient and harder to query
xlog.Infof(ctx, "user %s logged in from IP %s", userID, ip)

// ✅ Efficient and queryable
xlog.Info(ctx, "user logged in",
    field.String("user_id", userID),
    field.String("ip", ip),
)
Performance Anti-Patterns to Avoid
Anti-Pattern Impact Fix
WithOperation/WithFields in loop +4-7 allocs/iter, 32x slower Create context once before loop
WithOperationSpan per item +7 allocs/iter, 670ns Create span for batch
Printf-style logging String allocations Use structured fields
Unused enriched context Wasted allocations Pass fields directly if logging once
Measured Performance (Apple M4 Pro)

Based on comprehensive benchmarking with statistical significance (n=10):

Operation Time Allocations Use Case
Basic logging ~2ns 0 Hot path logging
Context reuse ~2ns 0 Multiple logs with same context
Context creation (WithOperation) ~61ns 4 Setup phase only
Context creation (WithFields) ~99ns 5 Setup phase only
WithOperationSpan ~670ns 7 Request/operation boundaries

Key Insight: The adapter overhead is negligible (~2ns) when using proper patterns (context reuse). Anti-patterns (create in loop) show 100% allocation increase.

License

MIT

Dependencies

Documentation

Overview

Package xlog provides a Go logging toolkit for context-aware structured logging.

It stores loggers in context, falls back to a global logger, supports zap and slog adapters, and includes OpenTelemetry tracing helpers.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddSpanEvent

func AddSpanEvent(ctx context.Context, name string, options ...trace.EventOption)

AddSpanEvent adds an event to the span extracted from the context. Events are timestamped occurrences that can include additional attributes. If no span is found or if the span is not recording, this is a no-op.

Example:

xlog.AddSpanEvent(ctx, "cache-miss")
xlog.AddSpanEvent(ctx, "retry", trace.WithAttributes(
    attribute.Int("attempt", 3),
))

func ContextWithLogger

func ContextWithLogger(ctx context.Context, logger Logger) context.Context

ContextWithLogger adds a logger to the context and returns a new context.

Example:

logger, _ := zap.NewProduction()
ctx := xlog.ContextWithLogger(context.Background(), logger)

func ContextWithTracer

func ContextWithTracer(ctx context.Context, tracer trace.Tracer) context.Context

ContextWithTracer returns a new context with the provided tracer attached. The tracer can be retrieved later using TracerFromContext.

func Debug

func Debug(ctx context.Context, msg string, fields ...xfield.Field)

Debug logs a Debug level message with structured fields. Logger is extracted from context. If logger is not found, the global logger is used.

Example:

xlog.Debug(ctx, "debug message", xlog.String("key", "value"))

func Debugf

func Debugf(ctx context.Context, template string, args ...any)

Debugf logs a formatted Debug level message. Logger is extracted from context. If logger is not found, the global logger is used.

Example:

xlog.Debugf(ctx, "value: %d, status: %s", 42, "ok")

func Error

func Error(ctx context.Context, msg string, fields ...xfield.Field)

Error logs an Error level message with structured fields. Logger is extracted from context. If logger is not found, the global logger is used.

Example:

xlog.Error(ctx, "database query error", xlog.Error(err))

func Errorf

func Errorf(ctx context.Context, template string, args ...any)

Errorf logs a formatted Error level message. Logger is extracted from context. If logger is not found, the global logger is used.

Example:

xlog.Errorf(ctx, "failed to process request: %v", err)

func Fatal

func Fatal(ctx context.Context, msg string, fields ...xfield.Field)

Fatal logs a Fatal level message with structured fields and terminates the program. Logger is extracted from context. If logger is not found, the global logger is used. Calls os.Exit(1) after logging.

Example:

xlog.Fatal(ctx, "critical error", xlog.Error(err))

func Fatalf

func Fatalf(ctx context.Context, template string, args ...any)

Fatalf logs a formatted Fatal level message and terminates the program. Logger is extracted from context. If logger is not found, the global logger is used. Calls os.Exit(1) after logging.

Example:

xlog.Fatalf(ctx, "failed to start server: %v", err)

func Info

func Info(ctx context.Context, msg string, fields ...xfield.Field)

Info logs an Info level message with structured fields. Logger is extracted from context. If logger is not found, the global logger is used.

Example:

xlog.Info(ctx, "request processed", xlog.Duration("took", time.Second))

func Infof

func Infof(ctx context.Context, template string, args ...any)

Infof logs a formatted Info level message. Logger is extracted from context. If logger is not found, the global logger is used.

Example:

xlog.Infof(ctx, "user %s logged in", userID)

func Panic

func Panic(ctx context.Context, msg string, fields ...xfield.Field)

Panic logs a Panic level message with structured fields and panics. Logger is extracted from context. If logger is not found, the global logger is used.

Example:

xlog.Panic(ctx, "unexpected state", xlog.String("state", state))

func Panicf

func Panicf(ctx context.Context, template string, args ...any)

Panicf logs a formatted Panic level message and panics. Logger is extracted from context. If logger is not found, the global logger is used.

Example:

xlog.Panicf(ctx, "invalid value: %v", value)

func RecordSpanError

func RecordSpanError(ctx context.Context, err error, options ...trace.EventOption)

RecordSpanError records an error on the span extracted from the context. It also sets the span status to Error with the error message. If no span is found or if the span is not recording, this is a no-op.

Example:

if err := doSomething(); err != nil {
    xlog.RecordSpanError(ctx, err, trace.WithStackTrace(true))
    return err
}

func ReplaceGlobalLogger

func ReplaceGlobalLogger(logger Logger) func()

ReplaceGlobalLogger replaces the global logger and returns a function to restore the previous logger. This function is thread-safe and can be called concurrently.

This is similar to zap.ReplaceGlobals() but works with any Logger implementation.

Example:

logger := xlog.NewZapAdapter(zapLogger)
restore := xlog.ReplaceGlobalLogger(logger)
defer restore() // Restore previous logger when done

func ReplaceTracerName

func ReplaceTracerName(name string) func()

ReplaceTracerName sets the global tracer name used when creating new tracers. This should be called during application initialization before any tracing operations. The default tracer name is "github.com/ruko1202/xlog". This function is thread-safe and can be called concurrently. Returns the previous tracer name.

func SetSpanAttributes

func SetSpanAttributes(ctx context.Context, kv ...attribute.KeyValue)

SetSpanAttributes adds attributes to the span extracted from the context. If no span is found or if the span is not recording, this is a no-op.

Example:

xlog.SetSpanAttributes(ctx,
    attribute.String("user_id", "123"),
    attribute.Int64("count", 42),
)

func SpanFromContext

func SpanFromContext(ctx context.Context) trace.Span

SpanFromContext extracts span from context using OpenTelemetry's standard API. If span is not found, returns a NoopSpan.

Example:

span := xlog.SpanFromContext(ctx)
span.SetAttributes(attribute.String("key", "value"))

func TracerFromContext

func TracerFromContext(ctx context.Context) trace.Tracer

TracerFromContext extracts a tracer from the context. If no tracer is found, returns the global tracer from otel.GetTracerProvider().

Example:

tracer := xlog.TracerFromContext(ctx)
ctx, span := tracer.Start(ctx, "my-operation")
defer span.End()

func Unwrap

func Unwrap[T any](logger Logger) (T, bool)

Unwrap returns the underlying backend logger of type T. Returns (zero, false) if the logger does not implement Unwrapper or the type does not match.

Example:

zapLogger, ok := xlog.Unwrap[*zap.Logger](logger)
slogLogger, ok := xlog.Unwrap[*slog.Logger](logger)

func Warn

func Warn(ctx context.Context, msg string, fields ...xfield.Field)

Warn logs a Warn level message with structured fields. Logger is extracted from context. If logger is not found, the global logger is used.

Example:

xlog.Warn(ctx, "slow query", xlog.Duration("took", time.Second*5))

func Warnf

func Warnf(ctx context.Context, template string, args ...any)

Warnf logs a formatted Warn level message. Logger is extracted from context. If logger is not found, the global logger is used.

Example:

xlog.Warnf(ctx, "retry attempts: %d", retryCount)

func WithFields

func WithFields(ctx context.Context, fields ...xfield.Field) context.Context

WithFields creates a new context with additional fields added to the logger. It extracts the logger from the context and adds the specified fields.

func WithOperation

func WithOperation(ctx context.Context, operation string, fields ...xfield.Field) context.Context

WithOperation creates a new context with a named logger for a specific operation. It extracts the logger from the context, adds the operation name, and includes any additional fields.

func WithOperationSpan

func WithOperationSpan(ctx context.Context, operation string, fields ...xfield.Field) (context.Context, trace.Span)

WithOperationSpan creates a new span for the given operation and attaches it to the context. It also creates a named logger with the operation name and the provided fields. The fields are added both to the logger and as span attributes. Returns the updated context with both the logger and span attached, along with the span itself.

Example:

ctx, span := xlog.WithOperationSpan(ctx, "process-payment",
    xlog.String("user_id", "123"),
    xlog.String("payment_id", "pay_xyz"),
)
defer span.End()

Types

type Logger

type Logger interface {
	// Debug logs a debug-level message with structured fields.
	Debug(msg string, fields ...xfield.Field)

	// Info logs an info-level message with structured fields.
	Info(msg string, fields ...xfield.Field)

	// Warn logs a warning-level message with structured fields.
	Warn(msg string, fields ...xfield.Field)

	// Error logs an error-level message with structured fields.
	Error(msg string, fields ...xfield.Field)

	// Fatal logs a fatal-level message with structured fields and terminates the program.
	Fatal(msg string, fields ...xfield.Field)

	// Panic logs a panic-level message with structured fields and panics.
	Panic(msg string, fields ...xfield.Field)

	// With creates a child logger with the given fields pre-attached.
	// All subsequent logs from this logger will include these fields.
	With(fields ...xfield.Field) Logger

	// Named creates a child logger with the given name appended.
	// This is useful for adding operation or component names to logs.
	Named(name string) Logger

	// Sync flushes any buffered log entries.
	// Applications should call Sync before exiting to ensure all logs are written.
	Sync() error
}

Logger is the interface that wraps the basic logging methods. This interface allows xlog to work with any logging backend (zap, slog, logrus, etc).

func GlobalLogger

func GlobalLogger() Logger

GlobalLogger returns the global logger. If no logger has been set via ReplaceGlobalLogger, returns NoopLogger. This function is thread-safe.

func L

func L() Logger

L is a shorthand for GlobalLogger(). This is similar to zap.L() and provides quick access to the global logger.

Example:

xlog.L().Info("message", xlog.String("key", "value"))

func LoggerFromContext

func LoggerFromContext(ctx context.Context) Logger

LoggerFromContext extracts logger from context. If logger is not found, returns the global logger.

Example:

logger := xlog.LoggerFromContext(ctx)
logger.Info("direct zap logger usage")

func NewNoopLogger

func NewNoopLogger() Logger

NewNoopLogger creates a new no-op logger.

func NewSlogAdapter

func NewSlogAdapter(logger *slog.Logger, options ...SlogOption) Logger

NewSlogAdapter creates a new SlogAdapter wrapping the given slog.Logger.

func NewSlogAdapterWithContext

func NewSlogAdapterWithContext(ctx context.Context, logger *slog.Logger, options ...SlogOption) Logger

NewSlogAdapterWithContext creates a new SlogAdapter with a context. If logger is nil, it uses slog.Default(). The context is used for all logging operations.

func NewZapAdapter

func NewZapAdapter(logger *zap.Logger) Logger

NewZapAdapter creates a new ZapAdapter wrapping the given zap.Logger.

type NoopLogger

type NoopLogger struct{}

NoopLogger is a logger that does nothing. This is used as a fallback when no logger is configured.

func (*NoopLogger) Debug

func (l *NoopLogger) Debug(_ string, _ ...xfield.Field)

Debug is a no-op implementation.

func (*NoopLogger) Error

func (l *NoopLogger) Error(_ string, _ ...xfield.Field)

Error is a no-op implementation.

func (*NoopLogger) Fatal

func (l *NoopLogger) Fatal(_ string, _ ...xfield.Field)

Fatal is a no-op implementation.

func (*NoopLogger) Info

func (l *NoopLogger) Info(_ string, _ ...xfield.Field)

Info is a no-op implementation.

func (*NoopLogger) Named

func (l *NoopLogger) Named(_ string) Logger

Named returns the same logger instance.

func (*NoopLogger) Panic

func (l *NoopLogger) Panic(msg string, _ ...xfield.Field)

Panic panics with the given message.

func (*NoopLogger) Sync

func (l *NoopLogger) Sync() error

Sync flushes any buffered log entries.

func (*NoopLogger) Warn

func (l *NoopLogger) Warn(_ string, _ ...xfield.Field)

Warn is a no-op implementation.

func (*NoopLogger) With

func (l *NoopLogger) With(_ ...xfield.Field) Logger

With returns the same logger instance.

type SlogAdapter

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

SlogAdapter adapts a slog.Logger to the xlog.Logger interface.

func (*SlogAdapter) Debug

func (s *SlogAdapter) Debug(msg string, fields ...xfield.Field)

Debug logs a debug-level message.

func (*SlogAdapter) Error

func (s *SlogAdapter) Error(msg string, fields ...xfield.Field)

Error logs an error-level message.

func (*SlogAdapter) Fatal

func (s *SlogAdapter) Fatal(msg string, fields ...xfield.Field)

Fatal logs a fatal-level message and terminates the program. Note: slog doesn't have a Fatal level, so we log as Error with a special marker and exit.

func (*SlogAdapter) Info

func (s *SlogAdapter) Info(msg string, fields ...xfield.Field)

Info logs an info-level message.

func (*SlogAdapter) Named

func (s *SlogAdapter) Named(name string) Logger

Named creates a child logger with the given name. In slog, this is implemented by adding a "logger" field with the name.

func (*SlogAdapter) Panic

func (s *SlogAdapter) Panic(msg string, fields ...xfield.Field)

Panic logs a panic-level message and panics. Note: slog doesn't have a Panic level, so we log as Error with a special marker and panic.

func (*SlogAdapter) Sync

func (s *SlogAdapter) Sync() error

Sync flushes any buffered log entries. Note: slog doesn't have a Sync method, so this is a no-op.

func (*SlogAdapter) Unwrap

func (s *SlogAdapter) Unwrap() *slog.Logger

Unwrap returns the underlying slog.Logger. This is useful for cases where you need direct access to slog-specific features.

func (*SlogAdapter) Warn

func (s *SlogAdapter) Warn(msg string, fields ...xfield.Field)

Warn logs a warning-level message.

func (*SlogAdapter) With

func (s *SlogAdapter) With(fields ...xfield.Field) Logger

With creates a child logger with pre-attached fields.

func (*SlogAdapter) WithContext

func (s *SlogAdapter) WithContext(ctx context.Context, fields ...xfield.Field) Logger

WithContext returns a new adapter with the given context.

type SlogOption

type SlogOption func(*SlogAdapter)

SlogOption is a function that configures a SlogAdapter.

func WithExitFunc

func WithExitFunc(fn func()) SlogOption

WithExitFunc sets a custom exit function (for testing).

func WithPanicFunc

func WithPanicFunc(fn func(string)) SlogOption

WithPanicFunc sets a custom panic function (for testing).

type Unwrapper

type Unwrapper[T any] interface {
	Unwrap() T
}

Unwrapper is an optional interface that a Logger may implement to expose the underlying backend logger (e.g. *zap.Logger, *slog.Logger).

type ZapAdapter

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

ZapAdapter adapts a zap.Logger to the xlog.Logger interface.

func (*ZapAdapter) Debug

func (z *ZapAdapter) Debug(msg string, fields ...xfield.Field)

Debug logs a debug-level message.

func (*ZapAdapter) Error

func (z *ZapAdapter) Error(msg string, fields ...xfield.Field)

Error logs an error-level message.

func (*ZapAdapter) Fatal

func (z *ZapAdapter) Fatal(msg string, fields ...xfield.Field)

Fatal logs a fatal-level message and terminates the program.

func (*ZapAdapter) Info

func (z *ZapAdapter) Info(msg string, fields ...xfield.Field)

Info logs an info-level message.

func (*ZapAdapter) Named

func (z *ZapAdapter) Named(name string) Logger

Named creates a child logger with the given name.

func (*ZapAdapter) Panic

func (z *ZapAdapter) Panic(msg string, fields ...xfield.Field)

Panic logs a panic-level message and panics.

func (*ZapAdapter) Sync

func (z *ZapAdapter) Sync() error

Sync flushes any buffered log entries.

func (*ZapAdapter) Unwrap

func (z *ZapAdapter) Unwrap() *zap.Logger

Unwrap returns the underlying zap.Logger. This is useful for cases where you need direct access to zap-specific features.

func (*ZapAdapter) Warn

func (z *ZapAdapter) Warn(msg string, fields ...xfield.Field)

Warn logs a warning-level message.

func (*ZapAdapter) With

func (z *ZapAdapter) With(fields ...xfield.Field) Logger

With creates a child logger with pre-attached fields.

Directories

Path Synopsis
Package xfield provides structured field constructors for logging.
Package xfield provides structured field constructors for logging.

Jump to

Keyboard shortcuts

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