Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mnah05-boiler-go-21-79.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

The logger package provides structured logging using zerolog with support for multiple output destinations including stdout, file, or both. It includes request-scoped logging and context extraction utilities.

Types

OutputConfig

Defines where logs should be written.
type OutputConfig struct {
    // FilePath is the path to the log file. If empty, no file logging.
    FilePath string
    // Stdout enables console output.
    Stdout bool
    // StdoutOnly disables file output and only logs to stdout.
    StdoutOnly bool
}
FilePath
string
Path to the log file. If empty, no file logging occurs
Stdout
bool
Enables console output with human-readable formatting
StdoutOnly
bool
Disables file output and only logs to stdout

Functions

New

Creates a new logger with default configuration (stdout only).
func New() zerolog.Logger
Returns: zerolog.Logger - Logger instance configured for stdout Configuration:
  • Human-readable console output
  • Timestamp: 2006-01-02 15:04:05 format
  • Log level: Info

NewWithOutput

Creates a new logger with configurable output destinations.
func NewWithOutput(cfg OutputConfig) zerolog.Logger
cfg
OutputConfig
Configuration for output destinations
Returns: zerolog.Logger - Configured logger instance Behavior:
  • Creates log directory if it doesn’t exist
  • Falls back to stdout on file errors
  • Supports multi-writer for both stdout and file
  • Automatically adds timestamps
  • Sets Info log level

Global

Returns the global fallback logger instance.
func Global() zerolog.Logger
Returns: zerolog.Logger - Global logger instance Usage: Used when a request-scoped logger is not available. Initialized lazily on first access.

FromEchoContext

Extracts the request-scoped logger from Echo context.
func FromEchoContext(c echo.Context) zerolog.Logger
c
echo.Context
Echo request context containing the logger
Returns: zerolog.Logger - Request-scoped logger with request_id Note: This function is typically used in handlers where the logger has been injected by the RequestLogger middleware.

Usage

Basic logging

import "boiler-go/pkg/logger"

func main() {
    // Create a basic logger
    log := logger.New()
    
    // Log messages
    log.Info().Msg("application started")
    log.Error().Err(err).Msg("operation failed")
    log.Debug().Str("user_id", id).Msg("processing request")
}

File logging

// Log to file only
log := logger.NewWithOutput(logger.OutputConfig{
    FilePath:   "logs/app.log",
    Stdout:     false,
    StdoutOnly: false,
})

Dual output (stdout and file)

// Log to both stdout and file
log := logger.NewWithOutput(logger.OutputConfig{
    FilePath:   "logs/app.log",
    Stdout:     true,
    StdoutOnly: false,
})

Configuration-based logging

// From cmd/api/main.go and cmd/worker/main.go
func newLogger(cfg *config.Config, defaultFile string) zerolog.Logger {
    outputCfg := logger.OutputConfig{}

    switch cfg.LogOutput {
    case "stdout":
        outputCfg.Stdout = true
        outputCfg.StdoutOnly = true
    case "file":
        outputCfg.Stdout = false
        outputCfg.StdoutOnly = false
        outputCfg.FilePath = cfg.LogFile
        if outputCfg.FilePath == "" {
            outputCfg.FilePath = defaultFile
        }
    case "both":
        outputCfg.Stdout = true
        outputCfg.StdoutOnly = false
        outputCfg.FilePath = cfg.LogFile
        if outputCfg.FilePath == "" {
            outputCfg.FilePath = defaultFile
        }
    }

    return logger.NewWithOutput(outputCfg)
}

func main() {
    cfg := config.Load(logger.New())
    log := newLogger(cfg, "logs/api.log")
    
    log.Info().Msg("server starting")
}

Request-scoped logging in handlers

import (
    "boiler-go/pkg/logger"
    "github.com/labstack/echo/v4"
)

func (h *HealthHandler) Check(c echo.Context) error {
    // Get request-scoped logger with request_id
    log := logger.FromEchoContext(c)
    
    log.Info().Msg("health check started")
    
    if err := h.db.Ping(ctx); err != nil {
        log.Error().Err(err).Msg("database health check failed")
        return c.JSON(500, map[string]string{"error": "database unavailable"})
    }
    
    log.Info().Msg("health check completed")
    return c.JSON(200, map[string]string{"status": "ok"})
}

Structured logging

log := logger.New()

// Add contextual fields
log.Info().
    Str("user_id", userID).
    Str("action", "login").
    Dur("duration", time.Since(start)).
    Msg("user logged in")

// Log with error
log.Error().
    Err(err).
    Str("operation", "database_query").
    Int("retry_count", retries).
    Msg("operation failed after retries")

Log levels

The logger is set to Info level by default. Available levels:
log.Trace().Msg("trace message")
log.Debug().Msg("debug message")
log.Info().Msg("info message")
log.Warn().Msg("warning message")
log.Error().Msg("error message")
log.Fatal().Msg("fatal message") // exits application

Best practices

  1. Use request-scoped loggers in HTTP handlers via FromEchoContext()
  2. Add structured fields instead of string formatting
  3. Use appropriate log levels (Info for operations, Error for failures)
  4. Include correlation IDs via request_id for distributed tracing
  5. Log file paths should be configurable via environment variables