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

# Logger

> Request logging middleware with customizable format and output.

## Overview

The `logger` middleware logs HTTP requests with configurable format, output destination, and filtering. It captures method, path, status, latency, and custom fields.

Use it when you need:

* HTTP request logging
* Access logs for debugging
* Request timing metrics

## Installation

```go theme={null}
import "github.com/go-mizu/mizu/middlewares/logger"
```

## Quick Start

```go theme={null}
app := mizu.New()

// Default logger (stdout)
app.Use(logger.New())

app.Get("/", func(c *mizu.Ctx) error {
    return c.Text(200, "Hello!")
})
// Output: 2024/01/15 10:30:00 | 200 | 1.2ms | GET /
```

## Configuration

### Options

| Option   | Type                   | Default        | Description               |
| -------- | ---------------------- | -------------- | ------------------------- |
| `Output` | `io.Writer`            | `os.Stdout`    | Log output destination    |
| `Format` | `string`               | Default format | Log format template       |
| `Skip`   | `func(*mizu.Ctx) bool` | `nil`          | Skip logging for requests |

### Format Tags

| Tag                | Description          |
| ------------------ | -------------------- |
| `${method}`        | HTTP method          |
| `${path}`          | Request path         |
| `${status}`        | Response status code |
| `${latency}`       | Request duration     |
| `${ip}`            | Client IP address    |
| `${host}`          | Request host         |
| `${protocol}`      | HTTP protocol        |
| `${referer}`       | Referer header       |
| `${user_agent}`    | User-Agent header    |
| `${bytes_out}`     | Response size        |
| `${query}`         | Query string         |
| `${header:X-Name}` | Custom header value  |

## Examples

### Default Logger

```go theme={null}
app.Use(logger.New())
```

### Custom Format

```go theme={null}
app.Use(logger.WithOptions(logger.Options{
    Format: "[${method}] ${path} -> ${status}\n",
}))
// Output: [GET] /api/users -> 200
```

### JSON Format

```go theme={null}
app.Use(logger.WithOptions(logger.Options{
    Format: `{"method":"${method}","path":"${path}","status":${status},"latency":"${latency}"}` + "\n",
}))
```

### Write to File

```go theme={null}
file, _ := os.OpenFile("access.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)

app.Use(logger.WithOptions(logger.Options{
    Output: file,
}))
```

### Skip Health Checks

```go theme={null}
app.Use(logger.WithOptions(logger.Options{
    Skip: func(c *mizu.Ctx) bool {
        return c.Request().URL.Path == "/health"
    },
}))
```

### Include Request ID

```go theme={null}
app.Use(logger.WithOptions(logger.Options{
    Format: "${header:X-Request-ID} | ${method} ${path} | ${status}\n",
}))
```

### Full Access Log

```go theme={null}
app.Use(logger.WithOptions(logger.Options{
    Format: `${ip} - [${time}] "${method} ${path} ${protocol}" ${status} ${bytes_out} "${referer}" "${user_agent}"` + "\n",
}))
```

## API Reference

### Functions

```go theme={null}
// New creates logger middleware with defaults
func New() mizu.Middleware

// WithOptions creates logger middleware with configuration
func WithOptions(opts Options) mizu.Middleware
```

## Technical Details

### Implementation Architecture

The logger middleware captures HTTP request metrics by wrapping the response writer and measuring execution time:

1. **Response Writer Wrapping**: A custom `responseWriter` type wraps the standard `http.ResponseWriter` to capture:
   * HTTP status code (defaults to 200)
   * Response size in bytes

2. **Timing Measurement**: Uses `time.Now()` before request processing and `time.Since()` after to calculate latency with nanosecond precision.

3. **Client IP Detection**: Implements intelligent IP extraction with fallback chain:
   * Checks `X-Forwarded-For` header (uses first IP if comma-separated list)
   * Checks `X-Real-IP` header
   * Falls back to `RemoteAddr` from the request

4. **Format String Processing**: The `formatLog` function performs template tag replacement:
   * Basic tags use `strings.ReplaceAll()` for simple substitution
   * Dynamic tags (`${header:name}`, `${form:name}`) use index-based parsing to extract custom values
   * Supports all standard HTTP request/response attributes

5. **Skip Function**: Optional predicate function allows conditional logging based on request context, executed before timing starts to avoid overhead.

### Performance Characteristics

* Minimal overhead: Single writer wrap and string replacement operations
* Zero allocations for skipped requests
* String concatenation deferred until after request completion
* No buffering of response body (streaming-friendly)

## Best Practices

* Skip logging for health check endpoints
* Use structured (JSON) format for log aggregation
* Include request IDs for tracing
* Log to stderr in containers for proper log handling

## Testing

The logger middleware includes comprehensive test coverage for all features and edge cases:

| Test Case                       | Description                                                                         | Expected Behavior                                                           |
| ------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `TestNew`                       | Default logger with standard output                                                 | Logs contain status code (200), HTTP method (GET), and request path (/test) |
| `TestWithOptions_CustomFormat`  | Custom format string with method, path, and status                                  | Output matches exact format: `[GET] /api -> 201\n`                          |
| `TestWithOptions_Skip`          | Skip function filtering health check endpoint                                       | No log output for /health endpoint, logs /api endpoint normally             |
| `TestWithOptions_Headers`       | Custom header extraction using `${header:X-Request-ID}`                             | Logs the exact value of the X-Request-ID header (test-123)                  |
| `TestWithOptions_AllTags`       | All available format tags (host, protocol, referer, user\_agent, bytes\_out, query) | Log contains all requested tag values from the HTTP request                 |
| `TestWithOptions_XForwardedFor` | Client IP extraction from X-Forwarded-For header                                    | Extracts first IP from comma-separated list (1.2.3.4)                       |
| `TestWithOptions_XRealIP`       | Client IP extraction from X-Real-IP header                                          | Uses X-Real-IP value (10.0.0.1) when X-Forwarded-For not present            |
| `TestDefaultOutput`             | Default output configuration (stdout)                                               | Middleware executes without panic, returns 200 status                       |

## Related Middlewares

* [requestid](/middlewares/requestid) - Request ID generation
* [requestlog](/middlewares/requestlog) - Detailed request logging
* [audit](/middlewares/audit) - Audit logging
* [timing](/middlewares/timing) - Server-Timing header
