Skip to content

Logging System

Blueprint provides a structured logging system built on top of zerolog that offers consistent logging patterns across different components.

Features

  • Structured logging with standardized field names
  • Context-aware logging for tracking requests across multiple services
  • Distributed tracing with trace and request IDs
  • Component-specific logging for HTTP and Kafka operations
  • Log levels for different severity of messages
  • Performance-oriented with minimal allocations

Basic Usage

import "github.com/oddbit-project/blueprint/log"

// Create a logger for a module
logger := log.New("mymodule")

// Log messages at different levels
logger.Info("Application started", log.KV{
    "version": "1.0.0",
})

logger.Debug("Processing item", log.KV{
    "item_id": 123,
})

// Log errors with stack traces
err := someOperation()
if err != nil {
    logger.Error(err, "Failed to process item", log.KV{
        "item_id": 123,
    })
}

Context-Aware Logging

import "github.com/oddbit-project/blueprint/log"

// Create a request context with trace ID
ctx, logger := log.NewRequestContext(context.Background(), "api")

// Add fields to the logger
ctx = log.WithField(ctx, "user_id", userId)

// Log using the context
log.Info(ctx, "Processing user request")

// Pass the context to other functions
processRequest(ctx, request)

// In other functions, retrieve the logger from context
func processRequest(ctx context.Context, req Request) {
    logger := log.FromContext(ctx)
    logger.Info("Processing request details")

    // Or use helper functions
    log.Info(ctx, "Alternative way to log")
}

HTTP Request Logging

import (
    "github.com/gin-gonic/gin"
    "github.com/oddbit-project/blueprint/log"
    "github.com/oddbit-project/provider/httpserver"
)

// Use the HTTP logging middleware
router.Use(httpserver.HTTPLogMiddleware("api"))

// Log within handlers
func handler(c *gin.Context) {
    // Get the request logger
    logger := httpserver.GetRequestLogger(c)

    // Or use helper functions
    httpserver.RequestInfo(c, "Processing API request", log.KV{
        "request_data": someData,
    })

    // Error handling
    if err := someOperation(); err != nil {
        httpserver.RequestError(c, err, "Operation failed")
        return
    }
}

Kafka Message Logging

Kafka consumer example:

import (
    "context"
    "github.com/oddbit-project/blueprint/log"
    "github.com/oddbit-project/blueprint/provider/kafka"
)

ctx := context.Background()

// Producer logging
producer, _ := kafka.NewProducer(cfg, nil)
err := producer.WriteJson(ctx, data)

// Consumer with logging
consumer, _ := kafka.NewConsumer(cfg, nil)
consumer.Subscribe(ctx, func(ctx context.Context, msg kafka.Message) error {
    // use consumer logger
    consumer.Logger.Info("processing message...")

    // log kafka message
    kafka.LogMessageReceived(consumer.Logger, msg, consumer.GetConfig().Group)    

    // Add your processing logic
    // ...

    return nil
})

Configuration

import "github.com/oddbit-project/blueprint/log"

// Create a configuration
cfg := log.NewDefaultConfig()
cfg.Level = "debug"           // log level: debug, info, warn, error
cfg.Format = "pretty"        // output format: pretty or json
cfg.IncludeTimestamp = true   // include timestamp in logs
cfg.IncludeCaller = true      // include caller information
cfg.IncludeHostname = true    // include hostname

// Configure the global logger
err := log.Configure(cfg)
if err != nil {
    panic(err)
}

Formatted Logging Methods

In addition to the standard logging methods, the logger provides formatted variants that support printf-style formatting:

logger := log.New("mymodule")

// Formatted logging methods
logger.Debugf("Processing item %d of %d", current, total)
logger.Infof("User %s logged in from %s", username, ipAddress)
logger.Warnf("Cache hit rate below threshold: %.2f%%", hitRate)
logger.Errorf(err, "Failed to process request %s", requestID)
logger.Fatalf(err, "Critical failure in %s", component)

Context-Based Formatted Logging

When using context-aware logging, formatted variants are also available:

// These functions extract the logger from context and use formatted output
log.Debugf(ctx, "Processing item %d", itemID)
log.Infof(ctx, "Request completed in %dms", duration)
log.Warnf(ctx, "Retry attempt %d of %d", attempt, maxRetries)
log.Errorf(ctx, err, "Failed to connect to %s", hostname)
log.Fatalf(ctx, err, "Unrecoverable error in %s", service)

Additional Utility Functions

Context Utilities

// MergeContextFields combines multiple field sets into one
fields := log.MergeContextFields(
    log.ContextFields{"user_id": userID},
    log.ContextFields{"request_id": reqID},
)

// NewTraceID generates a new unique trace ID
traceID := log.NewTraceID()

// ExtractLoggerFromContext retrieves the logger from context
logger := log.ExtractLoggerFromContext(ctx)

Logger Utility Methods

logger := log.New("mymodule")

// Get module information
info := logger.ModuleInfo()  // Returns the module name

// Get hostname
hostname := logger.Hostname()  // Returns the system hostname

// Set custom output writer
logger.WithOutput(customWriter)

// Access the underlying zerolog logger for advanced usage
zl := logger.GetZerolog()

Best Practices

  1. Include relevant fields: Add meaningful fields to help with debugging and analysis
  2. Be consistent with log levels:
  3. DEBUG: Detailed information for debugging
  4. INFO: General operational information
  5. WARN: Situations that might cause issues
  6. ERROR: Errors that prevent normal operation
  7. FATAL: Critical errors that require shutdown
  8. Sanitize sensitive data: Don't log passwords, tokens, or other sensitive information
  9. Use structured logging: Avoid string concatenation or formatting in log messages