Logging

Goose provides a flexible logging system with multiple formatters, processors, and output destinations.

Basic Usage

Inject the logger and use it:

type UserService struct {
    log types.Log `inject:""`
}

func (s *UserService) CreateUser(dto CreateUserDTO) (*User, error) {
    s.log.Info("Creating user", "email", dto.Email)

    user, err := s.repo.Create(dto)
    if err != nil {
        s.log.Error("Failed to create user", "error", err)
        return nil, err
    }

    s.log.Info("User created", "user_id", user.ID)
    return user, nil
}

Log Levels

Goose supports standard log levels:

// Debug - Detailed debugging information
s.log.Debug("Processing request", "request_id", id)

// Info - General information
s.log.Info("User logged in", "user_id", userID)

// Notice - Normal but significant events
s.log.Notice("Cache cleared", "cache_keys", count)

// Warning - Warning conditions
s.log.Warning("Rate limit approaching", "current", count, "limit", limit)

// Error - Error conditions
s.log.Error("Database connection failed", "error", err)

// Critical - Critical conditions
s.log.Critical("Service unavailable", "service", name)

// Alert - Action must be taken immediately
s.log.Alert("Security breach detected", "ip", ip)

// Emergency - System is unusable
s.log.Emergency("System crash", "error", err)

Configuring the Logger

Basic Setup

import (
    "github.com/awesome-goose/goose/log"
    "github.com/awesome-goose/goose/log/formatters"
    "github.com/awesome-goose/goose/log/processors"
)

func setupLogger() types.Log {
    return log.NewLog(
        log.AppLogChannel("app"),
        log.NewLogger(
            []types.Modifier{},
            formatters.NewLine(),
            processors.NewConsole(),
        ),
    )
}

With Formatters

import "github.com/awesome-goose/goose/log/formatters"

// Line format (human readable)
formatters.NewLine()
// Output: 2024-01-15 10:30:45 [app] INFO: User created | extra: [...]

// JSON format (machine readable)
formatters.NewJSON()
// Output: {"datetime":"...","channel":"app","level":"info","message":"User created",...}

// Syslog format
formatters.NewSyslog()

With Processors

import "github.com/awesome-goose/goose/log/processors"

// Console output
processors.NewConsole()

// File output (directory path - files are created by hour)
processors.NewFileProcessor("./storage/logs")
// Creates: ./storage/logs/2024/01/15/10.txt

// Syslog output
// processors.NewSyslog() // If available

With Modifiers

import "github.com/awesome-goose/goose/log/modifiers"

log.NewLogger(
    []types.Modifier{
        modifiers.NewColorTagsModifier(), // Colorized output
        modifiers.NewStackTrace(),         // Include stack traces
        modifiers.NewSystemInfo(),         // Include system info
        modifiers.NewUUID(),               // Add UUID to each log
    },
    formatters.NewLine(),
    processors.NewConsole(),
)

Multiple Channels

Configure different logging channels:

import (
    "github.com/awesome-goose/goose/log"
    "github.com/awesome-goose/goose/log/formatters"
    "github.com/awesome-goose/goose/log/modifiers"
    "github.com/awesome-goose/goose/log/processors"
)

logger := log.NewLog(
    log.AppLogChannel("console"),
    log.NewLogger(
        []types.Modifier{modifiers.NewColorTagsModifier()},
        formatters.NewLine(),
        processors.NewConsole(),
    ),
)

// Add file channel
logger.Add("file",
    log.NewLogger(
        []types.Modifier{},
        formatters.NewJSON(),
        processors.NewFileProcessor("./storage/logs"),
    ),
)

// Use specific channel
logger.Use("file").Info("This goes to file")
logger.Use("console").Info("This goes to console")

Structured Logging

Always use structured logging with key-value pairs:

// โœ… Good: Structured logging
s.log.Info("Order processed",
    "order_id", order.ID,
    "customer_id", order.CustomerID,
    "total", order.Total,
    "items", len(order.Items),
)

// โŒ Bad: String formatting
s.log.Info(fmt.Sprintf("Order %s processed for customer %s, total: $%.2f",
    order.ID, order.CustomerID, order.Total))

Request Logging

Log HTTP requests:

type RequestLoggerMiddleware struct {
    log types.Log `inject:""`
}

func (m *RequestLoggerMiddleware) Handle(ctx types.Context) error {
    start := time.Now()

    // Log request start
    m.log.Info("Request started",
        "method", ctx.Method(),
        "path", ctx.Path(),
        "ip", ctx.ClientIP(),
    )

    // After handler (using deferred logging)
    defer func() {
        duration := time.Since(start)
        m.log.Info("Request completed",
            "method", ctx.Method(),
            "path", ctx.Path(),
            "duration_ms", duration.Milliseconds(),
        )
    }()

    return nil
}

Error Logging

Log errors with context:

func (s *PaymentService) ProcessPayment(orderID string, amount float64) error {
    err := s.gateway.Charge(amount)
    if err != nil {
        s.log.Error("Payment processing failed",
            "order_id", orderID,
            "amount", amount,
            "error", err,
            "gateway", s.gateway.Name(),
        )
        return err
    }

    s.log.Info("Payment processed successfully",
        "order_id", orderID,
        "amount", amount,
    )
    return nil
}

Log Rotation

For production, configure log rotation:

// Using file processor with rotation
log.NewFileProcessor("/var/log/app.log",
    log.WithMaxSize(100),      // 100 MB max
    log.WithMaxBackups(10),    // Keep 10 backups
    log.WithMaxAge(30),        // 30 days retention
    log.WithCompress(true),    // Compress old logs
)

Production Configuration

func NewProductionLogger() types.Log {
    return log.NewLog(
        log.AppLogChannel("production"),
        // File logging with JSON format
        log.NewLogger(
            []types.Modifier{
                log.NewUuidModifier(),
                log.NewSystemInfoModifier(),
            },
            log.NewJsonFormatter(),
            log.NewFileProcessor("/var/log/app.log"),
        ),
    )
}

Development Configuration

func NewDevelopmentLogger() types.Log {
    return log.NewLog(
        log.AppLogChannel("development"),
        // Console logging with colors
        log.NewLogger(
            []types.Modifier{
                log.NewColorModifier(),
                log.NewStackTraceModifier(),
            },
            log.NewLineFormatter(),
            log.NewConsoleProcessor(),
        ),
    )
}

Best Practices

1. Use Appropriate Log Levels

// Debug: For development details
s.log.Debug("SQL query executed", "query", sql, "duration", dur)

// Info: For normal operations
s.log.Info("User registered", "user_id", id)

// Warning: For recoverable issues
s.log.Warning("Cache miss", "key", key)

// Error: For errors that need attention
s.log.Error("API call failed", "error", err)

2. Include Context

// โœ… Good: Rich context
s.log.Error("Failed to send email",
    "user_id", userID,
    "email", email,
    "template", template,
    "error", err,
    "attempt", attemptNumber,
)

// โŒ Bad: No context
s.log.Error("Email failed")

3. Don't Log Sensitive Data

// โŒ Bad: Logging sensitive data
s.log.Info("User login", "password", password)

// โœ… Good: Mask or omit
s.log.Info("User login", "user_id", userID)

4. Use Consistent Keys

// โœ… Good: Consistent naming
s.log.Info("Operation", "user_id", id)
s.log.Info("Another op", "user_id", id)

// โŒ Bad: Inconsistent naming
s.log.Info("Operation", "userId", id)
s.log.Info("Another op", "user", id)

Next Steps