Skip to main content
Mizu uses Go’s slog package for structured logging. Every request gets logged automatically, and you can add custom log messages anywhere in your handlers.

What is structured logging?

Traditional logging writes plain text:
2024-01-15 10:30:45 User 123 logged in from 192.168.1.1
Structured logging writes key-value pairs:
{"time":"2024-01-15T10:30:45Z","level":"INFO","msg":"user logged in","user_id":123,"ip":"192.168.1.1"}
Structured logs are easier to search, filter, and analyze with tools like Elasticsearch, Loki, or CloudWatch.

Built-in request logging

When you create a Mizu app, request logging is enabled by default:
app := mizu.New()  // Logger middleware included
Each request produces a log entry like:
INFO request status=200 method=GET path=/users duration_ms=12 remote_ip=127.0.0.1

Logging in handlers

Access the logger from the context to add your own log messages:
func getUser(c *mizu.Ctx) error {
    id := c.Param("id")

    c.Logger().Info("fetching user", "user_id", id)

    user, err := database.FindUser(id)
    if err != nil {
        c.Logger().Error("database error", "error", err, "user_id", id)
        return err
    }

    c.Logger().Debug("user found", "user_id", id, "name", user.Name)
    return c.JSON(200, user)
}

Log levels

LevelMethodWhen to use
Debugc.Logger().Debug()Detailed info for debugging
Infoc.Logger().Info()Normal operations
Warnc.Logger().Warn()Something unexpected but recoverable
Errorc.Logger().Error()Something failed
c.Logger().Debug("checking cache", "key", cacheKey)
c.Logger().Info("user logged in", "user_id", userID)
c.Logger().Warn("cache miss", "key", cacheKey)
c.Logger().Error("payment failed", "error", err, "order_id", orderID)

Configuring the logger

Logger middleware options

Configure the built-in request logger:
app.Use(mizu.Logger(mizu.LoggerOptions{
    Mode:            mizu.Dev,          // Output format
    Color:           true,              // Colored output
    Logger:          customLogger,      // Custom slog.Logger
    RequestIDHeader: "X-Request-Id",    // Header name for request ID
    UserAgent:       true,              // Include User-Agent in logs
    Output:          os.Stderr,         // Where to write logs
}))

Log modes

ModeOutputUse case
mizu.AutoText for terminals, JSON otherwiseDefault - adapts automatically
mizu.DevHuman-readable textLocal development
mizu.ProdJSONProduction log aggregators
// Development: colored text output
app.Use(mizu.Logger(mizu.LoggerOptions{
    Mode:  mizu.Dev,
    Color: true,
}))

// Production: JSON for log aggregators
app.Use(mizu.Logger(mizu.LoggerOptions{
    Mode: mizu.Prod,
}))

Custom logger

Replace the default logger with your own:
import "log/slog"

// Create a custom logger
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))

// Set it on the app
app.SetLogger(logger)
All handlers and middleware will use this logger via c.Logger().

Request ID tracking

The logger middleware automatically generates or propagates a request ID for each request. This helps trace requests across services.
app.Use(mizu.Logger(mizu.LoggerOptions{
    RequestIDHeader: "X-Request-Id",  // Use this header
}))
The request ID is:
  1. Read from the incoming request header (if present)
  2. Generated automatically (if not present)
  3. Added to the response headers
  4. Included in all log entries

Example output

Development mode (text)

10:30:45 INFO request status=200 method=GET path=/users duration_ms=12 request_id=abc123
10:30:45 INFO fetching user user_id=42
10:30:45 INFO request status=200 method=GET path=/users/42 duration_ms=8 request_id=def456

Production mode (JSON)

{"time":"2024-01-15T10:30:45Z","level":"INFO","msg":"request","status":200,"method":"GET","path":"/users","duration_ms":12,"request_id":"abc123"}

Complete example

package main

import (
    "github.com/go-mizu/mizu"
)

func main() {
    app := mizu.New()

    app.Get("/users/{id}", func(c *mizu.Ctx) error {
        id := c.Param("id")

        // Add context to your logs
        c.Logger().Info("fetching user",
            "user_id", id,
            "method", c.Request().Method,
        )

        user := User{ID: id, Name: "Alice"}
        return c.JSON(200, user)
    })

    app.Listen(":3000")
}

Summary

FeatureHow
Log in handlerc.Logger().Info("message", "key", value)
Log levelsDebug, Info, Warn, Error
Configure formatmizu.Logger(mizu.LoggerOptions{Mode: mizu.Prod})
Custom loggerapp.SetLogger(myLogger)
Request IDAutomatic with RequestIDHeader option
Structured logging makes debugging easier and integrates well with modern observability tools.