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 // add a "hostname" field (os.Hostname())
// Configure the global logger
err := log.Configure(cfg)
if err != nil {
panic(err)
}
log.Configure() also applies process-wide zerolog settings (timestamp format and global level), which are not safe
to change while other goroutines are logging — call it once, during startup. Building a logger with cfg.Logger() or
cfg.ModuleLogger() has no process-wide effect, and such a logger uses zerolog's default timestamp layout rather than
cfg.TimeFormat.
With IncludeHostname, every line carries a hostname field (log.LogHostnameKey) holding os.Hostname(). This
applies to loggers built with cfg.Logger() or cfg.ModuleLogger(), and to log.New() / log.NewWithComponent()
loggers created after log.Configure(); a logger created before Configure() does not carry it. Do not add a
hostname field of your own while it is enabled, or JSON lines carry the key twice.
With IncludeCaller, the caller field reports the code that called the logging method. cfg.CallerSkipFrames
(default 3) controls that; raise it if you wrap the logger in helpers of your own, one per extra call level.
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¶
- Include relevant fields: Add meaningful fields to help with debugging and analysis
- Be consistent with log levels:
- DEBUG: Detailed information for debugging
- INFO: General operational information
- WARN: Situations that might cause issues
- ERROR: Errors that prevent normal operation
- FATAL: Critical errors that require shutdown
- Sanitize sensitive data: Don't log passwords, tokens, or other sensitive information
- Use structured logging: Avoid string concatenation or formatting in log messages