Skip to main content

Logging with slog

In this lesson, you will learn how to add structured logging to your Mizu applications using Go’s built-in log/slog package. Logs help you see what your app is doing, measure how long things take, and understand when something goes wrong. Mizu already includes a built-in request logger, so every request is automatically logged in a human-readable format by default. You can customize the log output, format, and level using your own slog setup.

Code

Create a file named main.go and add this code:
package main

import (
	"fmt"
	"log/slog"
	"os"
	"time"

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

// Example route that adds custom log entries
func home(c *mizu.Ctx) error {
	c.Logger().Info("user visited home",
		"method", c.Request().Method,
		"path", c.Request().URL.Path,
		"client_ip", c.ClientIP(),
	)
	return c.Text(200, "Home page")
}

// Simulate a slow route
func slow(c *mizu.Ctx) error {
	start := time.Now()
	time.Sleep(2 * time.Second)
	elapsed := time.Since(start)

	c.Logger().Info("slow request finished",
		"path", c.Request().URL.Path,
		"duration", elapsed.String(),
	)
	return c.Text(200, "Finished slow operation")
}

// Simulate an error and log the details
func fail(c *mizu.Ctx) error {
	err := doSomething()
	if err != nil {
		c.Logger().Error("operation failed",
			"error", err,
			"user_agent", c.Request().UserAgent(),
			"client_ip", c.ClientIP(),
		)
		return c.Text(500, "Something went wrong")
	}
	return c.Text(200, "Success")
}

func doSomething() error {
	return fmt.Errorf("database connection lost")
}

func main() {
	// Mizu logs in plain text by default.
	// You can still attach your own structured logger if you want.
	handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})
	logger := slog.New(handler)

	app := mizu.New(
		mizu.WithLogger(logger),
	)

	// Mizu automatically logs every request,
	// so you do not need to add another logging middleware.

	app.Get("/", home)
	app.Get("/slow", slow)
	app.Get("/fail", fail)

	if err := app.Listen(":8080"); err != nil {
		logger.Error("server failed", "error", err)
	}
}

Run

Start the server:
go run .
Then open these routes in your browser:
INFO request completed method=GET path=/ status=200 duration=1.3ms
INFO user visited home method=GET path=/ client_ip=127.0.0.1
INFO request completed method=GET path=/slow status=200 duration=2.001s
INFO slow request finished path=/slow duration=2.001s
ERROR operation failed error=database connection lost user_agent=curl/8.1.0 client_ip=127.0.0.1
By default, Mizu logs all requests using a readable text format. If you run your app in a terminal, it will even use color output if your environment supports it.

How it works

Mizu automatically adds a request logger middleware when your app starts. It logs details such as:
  • HTTP method
  • Path
  • Status code
  • Duration
  • Any errors returned by handlers
When you pass a custom slog.Logger using mizu.WithLogger, all logs from Mizu and your handlers use that logger instance.
FeatureWhat it doesExample
Built-in logsLogs every request automaticallyINFO request completed method=GET path=/
mizu.WithLoggerUse your own slog configurationapp := mizu.New(mizu.WithLogger(logger))
c.Logger()Log custom messages inside a handlerc.Logger().Info("event", "path", c.Request().URL.Path)
c.ClientIP()Get the visitor’s IP for logsUseful for tracking and debugging

Try something new

You can switch to JSON output for production environments:
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})
logger := slog.New(handler)
That will print structured logs like this:
{
  "time": "2025-11-04T10:30:00Z",
  "level": "INFO",
  "msg": "request completed",
  "method": "GET",
  "path": "/slow",
  "status": 200,
  "duration": "2.001s"
}
You can also set LevelDebug to get more detailed logs during development. Logging gives you a clear picture of what your app is doing, helps you understand user behavior, and makes it easy to spot and fix problems. Continue to the next lesson: Request IDs and Correlation to learn how to attach unique IDs to each request for tracing across logs and services.