Logger

Nimbus uses uber-go/zap for structured, high-performance logging. The logger package provides a pre-configured *zap.SugaredLogger and convenience functions.

Quick start

Import the logger package and call the level functions directly. The global logger is initialised automatically on import.

import "github.com/CodeSyncr/nimbus/logger"

logger.Info("server started", "port", 3333)
logger.Debug("loading config", "path", ".env")
logger.Warn("cache miss", "key", "user:42")
logger.Error("query failed", "error", err)

Log levels

The logger supports four primary levels. Each accepts a message string followed by key-value pairs for structured fields.

  • logger.Debug(msg, keysAndValues...) — verbose detail for development.
  • logger.Info(msg, keysAndValues...) — general operational events.
  • logger.Warn(msg, keysAndValues...) — potential issues that don't stop execution.
  • logger.Error(msg, keysAndValues...) — errors that need attention.
  • logger.Fatal(msg, keysAndValues...) — logs the message then calls os.Exit(1).

Structured fields

Pass alternating key-value pairs after the message. Zap encodes them as structured fields in the log output.

logger.Info("user login",
    "userID", 42,
    "email", "alice@example.com",
    "ip", "192.168.1.1",
)
// Output: 2026-03-08T12:00:00Z  INFO  user login  {"userID": 42, "email": "alice@example.com", "ip": "192.168.1.1"}

Child loggers with context

logger.With(keysAndValues...) returns a new *zap.SugaredLogger that includes the given fields in every subsequent log line. Useful for adding request-scoped context.

reqLogger := logger.With("requestID", "abc-123", "method", "GET")
reqLogger.Infow("handling request", "path", "/users")
reqLogger.Infow("query complete", "rows", 15)

Logging in middleware

The built-in middleware.Logger() logs every request. You can also add custom logging in your own middleware.

func RequestLogger() router.Middleware {
    return func(next router.HandlerFunc) router.HandlerFunc {
        return func(c *http.Context) error {
            start := time.Now()
            err := next(c)
            logger.Info("request",
                "method", c.Request().Method,
                "path", c.Request().URL.Path,
                "duration", time.Since(start).String(),
            )
            return err
        }
    }
}

Logging in handlers

func createUser(c *http.Context) error {
    logger.Info("creating user", "ip", c.Request().RemoteAddr)
    // ... create user ...
    logger.Info("user created", "userID", user.ID)
    return c.JSON(201, user)
}

Custom configuration

Replace the global logger with logger.Set() to customise encoding, level, or output. The default uses console encoding at Info level with ISO 8601 timestamps.

import (
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "github.com/CodeSyncr/nimbus/logger"
)

cfg := zap.NewProductionConfig()
cfg.Level = zap.NewAtomicLevelAt(zapcore.DebugLevel)
cfg.Encoding = "json"
l, _ := cfg.Build()
logger.Set(l)

Default configuration

Out of the box, the logger is configured with:

  • Production config base (sampling, caller info)
  • Console encoding for readable terminal output
  • Info level (Debug messages hidden by default)
  • ISO 8601 timestamps

Request-scoped logger

Use logger.ForRequest(c) to get a logger that automatically includes the request_id (set by the RequestID middleware). This correlates all log lines for a single request.

import "github.com/CodeSyncr/nimbus/logger"

func CreateOrder(c *http.Context) error {
    log := logger.ForRequest(c)
    log.Info("creating order", "user_id", userID)
    // ... create order ...
    log.Info("order created", "order_id", order.ID)
    // All log lines include {"request_id": "a1b2c3d4..."}
    return c.JSON(201, order)
}

Attach a custom scoped logger to the context with logger.WithContext:

// In middleware — enrich logger with user info
func UserLogger() router.Middleware {
    return func(next router.HandlerFunc) router.HandlerFunc {
        return func(c *http.Context) error {
            l := logger.ForRequest(c).With("user_id", getUserID(c))
            logger.WithContext(c, l)
            return next(c)
        }
    }
}

Log rotation

For file-based logging in production, use logger.RotatingWriter to automatically rotate log files when they exceed a size threshold and keep a limited number of backups.

import "github.com/CodeSyncr/nimbus/logger"

writer := logger.NewRotatingWriter(logger.RotationConfig{
    Path:       "storage/logs/app.log",
    MaxSizeMB:  100,  // Rotate at 100 MB (default)
    MaxBackups: 5,    // Keep 5 old log files (default)
})
defer writer.Sync()

// Use as an io.Writer for custom zap configuration:
core := zapcore.NewCore(
    zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
    zapcore.AddSync(writer),
    zap.InfoLevel,
)
l := zap.New(core).Sugar()
logger.Set(l)

Rotated files are named app-2026-03-16T12-00-00.log. Oldest backups are deleted when MaxBackups is exceeded.