---
title: "Access logs"
url: "/docs/observe/access-logs/"
kind: "feature"
summary: "Record every request and connection Arion handles, with Envoy-compatible format strings, file/stdout/stderr sinks and rotation."
---

> Arion site index: [/llms.txt](/llms.txt)

# Access logs

Record every request and connection Arion handles, with Envoy-compatible format strings, file/stdout/stderr sinks and rotation.

Access logs record each request and connection Arion handles — method, path, status, timings, bytes — in a format you control. Use them for traffic audit and per-request debugging; for aggregate numbers use [metrics](@/docs/observe/metrics.md) instead.

## Before you begin

Access logging is a build feature: the binary must be compiled with `--features access-log`. Log entries are formatted on the worker, then handed to dedicated logger tasks over a channel, so writing never blocks request processing (unless you ask it to — see below).

## Enable it

Two pieces of configuration work together. The top-level `access_logging` section switches the logging service on and tunes it:

```yaml,name=arion.yaml,arion=fragment
access_logging:
  num_instances: 1      # parallel logger tasks
  queue_length: 1024    # buffered entries per logger
  blocking: false       # false: drop entries when the queue is full; true: wait
```

Sinks then attach where the traffic is, using standard Envoy syntax: on a listener, inside `http_connection_manager`, or inside `tcp_proxy`. Each attachment point can have its own format and several sinks at once.

```yaml,name=arion.yaml,arion=fragment
# inside the HttpConnectionManager typed_config
access_log:
  - name: envoy.access_loggers.file
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
      path: /var/log/arion/access.log
      log_format:
        text_format: |
          [%START_TIME%] %REQ(:METHOD)% %REQ(:PATH)% %RESPONSE_CODE% %DURATION% %UPSTREAM_HOST%
```

## The format string

`text_format` uses the Envoy command-operator grammar: literal text plus `%OPERATOR%` placeholders, with `%REQ(name)%` and `%RESP(name)%` for request and response headers. `%START_TIME%` is the request start (it honors the top-level `timezone` setting), `%RESPONSE_CODE%` the downstream status, `%DURATION%` the total milliseconds, `%UPSTREAM_HOST%` the selected endpoint. An operator with no value renders as `-`. A placeholder Arion does not recognize is a startup (or xDS rejection) error, never silently ignored. When `log_format` is omitted, Arion writes Envoy's default HTTP access log line. The full table of supported operators is in the [access log operator reference](@/reference/observability/access-log-operators.md).

## Variations

### Log to stdout or stderr

Use `envoy.access_loggers.stdout` with `type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog` (or the `stderr`/`StderrAccessLog` pair). Both accept the same `log_format`. This is the usual choice in containers.

### Rotate files

File sinks rotate based on the `access_logging` section: `log_rotation` (`minutely`, `hourly` or `daily`) rotates on a schedule, `log_max_size` (bytes) rotates on size, and `max_log_files` (default 10) caps how many rotated files are kept. Without either setting, files grow unbounded. Files are created lazily, on the first entry.

### Custom operators

You can inject your own fields into log lines without changing Arion. Register the names under `access_logging.custom_operators`, name a header to inspect at one of the lifecycle hooks (`incoming_request_header`, `upstream_request_header`, `incoming_response_header`, `downstream_response_header`, the two `ext_proc_*` hooks, or `wasm_header` for values set by a WASM filter), then use `%user_id%` like any other operator:

```yaml,name=arion.yaml,arion=fragment
access_logging:
  blocking: false
  incoming_request_header: x-arion-metadata
  custom_operators: ["user_id", "session_id"]
```

The header value must be a base64-encoded JSON object; its top-level keys fill the matching operators. Built-in operators can never be overwritten this way, so audit fields stay tamper-proof, and a malformed payload logs a warning and renders `-` rather than failing the request.

## Verify

Start Arion, send a request, and read the sink:

```bash
curl -s http://localhost:8080/ >/dev/null
tail -n1 /var/log/arion/access.log
```

Expected: one line per request in your format, for example `[2026-09-28T10:15:04.512Z] GET / 200 3 127.0.0.1:5678`.

## Limits

- Only `file`, `stdout` and `stderr` loggers exist; `fluentd`, `http_grpc`, `tcp_grpc`, `opentelemetry` and `wasm` loggers are not supported and are rejected at load.
- `json_format` is not supported — text formats only.
- Access log `filter` conditions are not supported; every request at the attachment point is logged.
- Listener-level entries count whole connections, so byte and duration operators there are per-connection totals, not per-request.
