> ## Documentation Index
> Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Logging with slog

> Learn how to add structured logging with slog.

# 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:

```go theme={null}
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() {
	app := mizu.New()

	// Mizu logs in plain text by default.
	// You can attach your own structured logger if you want.
	handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})
	logger := slog.New(handler)
	app.SetLogger(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:

* [http://localhost:8080/](http://localhost:8080/) <br />
  You will see:
  `Home page`
  And in your terminal:

```
INFO request completed method=GET path=/ status=200 duration=1.3ms
INFO user visited home method=GET path=/ client_ip=127.0.0.1
```

* [http://localhost:8080/slow](http://localhost:8080/slow) <br />
  Wait a few seconds, then you will see:
  `Finished slow operation`
  And in your terminal:

```
INFO request completed method=GET path=/slow status=200 duration=2.001s
INFO slow request finished path=/slow duration=2.001s
```

* [http://localhost:8080/fail](http://localhost:8080/fail) <br />
  You will see:
  `Something went wrong`
  And in your terminal:

```
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 set a custom `slog.Logger` using `app.SetLogger()`, all logs from Mizu and your handlers use that logger instance.

| Feature           | What it does                         | Example                                                  |
| ----------------- | ------------------------------------ | -------------------------------------------------------- |
| Built-in logs     | Logs every request automatically     | `INFO request completed method=GET path=/`               |
| `app.SetLogger()` | Use your own `slog` configuration    | `app.SetLogger(logger)`                                  |
| `c.Logger()`      | Log custom messages inside a handler | `c.Logger().Info("event", "path", c.Request().URL.Path)` |
| `c.ClientIP()`    | Get the visitor's IP for logs        | Useful for tracking and debugging                        |

### Try something new

You can switch to JSON output for production environments:

```go theme={null}
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})
logger := slog.New(handler)
```

That will print structured logs like this:

```json theme={null}
{
  "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](./011-request-ids-correlation) to learn how to attach unique IDs to each request for tracing across logs and services.
