The log package initializes the default slog logger with tint for colorized, structured output and provides utilities to inject request-scoped values from context.
Installation
import "github.com/aarock1234/go-template/pkg/log"
Simply importing the package is enough to configure the logger:
import (
"log/slog"
_ "github.com/aarock1234/go-template/pkg/log" // Initialize logger
)
Features
- Colorized output: Automatically detects TTY and enables colors
- Structured logging: Built on Go’s standard
log/slog
- Context injection: Add request-scoped values to all log records
- Configurable log level: Set via
LOG_LEVEL environment variable
- Human-readable timestamps: Formatted as “Mon, Jan 2 2006, 3:04:05 pm MST”
Quick Start
package main
import (
"context"
"log/slog"
"github.com/aarock1234/go-template/pkg/log"
)
func main() {
// Basic logging
slog.Info("server started", slog.Int("port", 8080))
slog.Debug("debug information", slog.String("module", "main"))
slog.Warn("warning message")
slog.Error("error occurred", slog.String("error", "connection failed"))
// Context-aware logging
ctx := log.WithIdempotencyKey(context.Background(), "req-123")
slog.InfoContext(ctx, "processing request") // Includes idempotency_key
}
Functions
WithIdempotencyKey
Returns a copy of the context carrying the given idempotency key.
func WithIdempotencyKey(ctx context.Context, key string) context.Context
The idempotency key to inject into log records
Example:
func handleRequest(w http.ResponseWriter, r *http.Request) {
// Extract idempotency key from header
key := r.Header.Get("Idempotency-Key")
ctx := log.WithIdempotencyKey(r.Context(), key)
// All logs in this context will include the idempotency key
slog.InfoContext(ctx, "processing request")
// Output: ... msg="processing request" idempotency_key=req-123
processPayment(ctx)
}
func processPayment(ctx context.Context) {
slog.InfoContext(ctx, "payment processed")
// Output: ... msg="payment processed" idempotency_key=req-123
}
Types
ContextHandler
Wraps an slog.Handler to inject request-scoped values from the context into every log record.
type ContextHandler struct {
slog.Handler
}
Methods:
func (h *ContextHandler) Handle(ctx context.Context, r slog.Record) error
Logs a slog.Record with request-scoped values extracted from the context. Currently supports:
idempotency_key - Injected when context contains an idempotency key
Configuration
Log Level
Set the log level using the LOG_LEVEL environment variable:
Supported values:
debug - Show all logs including debug messages
info (default) - Show info, warn, and error logs
warn or warning - Show only warnings and errors
error - Show only errors
Example:
# Development
LOG_LEVEL=debug
# Production
LOG_LEVEL=info
Color Output
Colors are automatically enabled when:
- Writing to a TTY (terminal)
- Using Windows: Colors work via
go-colorable
Colors are disabled when:
- Output is redirected to a file
- Running in a non-TTY environment (e.g., CI/CD)
Logs are formatted with tint’s colorized, structured output:
Mon, Jan 2 2006, 3:04:05 pm MST INF server started port=8080
Mon, Jan 2 2006, 3:04:06 pm MST DBG debug message module=main
Mon, Jan 2 2006, 3:04:07 pm MST WRN warning message
Mon, Jan 2 2006, 3:04:08 pm MST ERR error occurred error="connection failed"
With context values:
Mon, Jan 2 2006, 3:04:09 pm MST INF processing request idempotency_key=req-123
Usage Examples
Basic Structured Logging
import "log/slog"
// Simple message
slog.Info("application started")
// With attributes
slog.Info("user logged in",
slog.String("user_id", "123"),
slog.String("ip", "192.168.1.1"),
)
// Grouped attributes
slog.Info("request completed",
slog.Group("request",
slog.String("method", "POST"),
slog.String("path", "/api/users"),
),
slog.Int("status", 201),
)
HTTP Middleware
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Add idempotency key to context
ctx := r.Context()
if key := r.Header.Get("Idempotency-Key"); key != "" {
ctx = log.WithIdempotencyKey(ctx, key)
}
// Log the request
slog.InfoContext(ctx, "incoming request",
slog.String("method", r.Method),
slog.String("path", r.URL.Path),
)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
Error Logging
func processData(ctx context.Context) error {
data, err := fetchData()
if err != nil {
slog.ErrorContext(ctx, "failed to fetch data",
slog.String("error", err.Error()),
)
return err
}
slog.InfoContext(ctx, "data processed", slog.Int("records", len(data)))
return nil
}
Creating a Logger with Additional Context
// Create a logger with persistent attributes
logger := slog.With(
slog.String("service", "payment-processor"),
slog.String("version", "1.0.0"),
)
// All logs from this logger include the attributes
logger.Info("service started") // Includes service=payment-processor version=1.0.0
logger.Error("payment failed", slog.String("error", "insufficient funds"))
Dependencies
The package uses:
Best Practices
- Use structured logging: Prefer key-value attributes over string formatting
// Good
slog.Info("user created", slog.String("user_id", id))
// Avoid
slog.Info(fmt.Sprintf("user %s created", id))
- Use context-aware logging: Pass context through your application
func HandleRequest(ctx context.Context) {
slog.InfoContext(ctx, "processing") // Includes context values
}
-
Set appropriate log levels: Use debug for development, info for production
-
Add persistent context: Create loggers with common attributes
logger := slog.With(slog.String("module", "database"))