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

# Logging

# Logging

OpenClaw logs in two places:

* **File logs** (JSON lines) written by the Gateway.
* **Console output** shown in terminals and the Control UI.

This page explains where logs live, how to read them, and how to configure log
levels, formats, rotation, and retention.

## Where logs live

By default, the Gateway writes a rolling log file under:

`/tmp/openclaw/openclaw-YYYY-MM-DD.log`

The date uses the gateway host's local timezone.

> **Note:** `/tmp` is periodically purged by the OS. For persistent log retention, use `openclaw logs --follow` for live output, or override the log path to a stable location such as `~/.openclaw/logs/openclaw.log`.

You can override this in `~/.openclaw/openclaw.json`:

```json theme={"dark"}
{
  "logging": {
    "file": "/path/to/openclaw.log"
  }
}
```

## How to read logs

### CLI: live tail (recommended)

Use the CLI to tail the gateway log file via RPC:

```bash theme={"dark"}
openclaw logs --follow
```

Output modes:

* **TTY sessions**: pretty, colorized, structured log lines.
* **Non-TTY sessions**: plain text.
* `--json`: line-delimited JSON (one log event per line).
* `--plain`: force plain text in TTY sessions.
* `--no-color`: disable ANSI colors.

In JSON mode, the CLI emits `type`-tagged objects:

* `meta`: stream metadata (file, cursor, size)
* `log`: parsed log entry
* `notice`: truncation / rotation hints
* `raw`: unparsed log line

If the Gateway is unreachable, the CLI prints a short hint to run:

```bash theme={"dark"}
openclaw doctor
```

### Control UI (web)

The Control UI’s **Logs** tab tails the same file using `logs.tail`.
See [/web/control-ui](/web/control-ui) for how to open it.

### Channel-only logs

To filter channel activity (WhatsApp/Telegram/etc), use:

```bash theme={"dark"}
openclaw channels logs --channel whatsapp
```

## Log formats

### File logs (JSONL)

Each line in the log file is a JSON object. The CLI and Control UI parse these
entries to render structured output (time, level, subsystem, message).

### Rotation and retention

The main Gateway file log rotates before an append would push the active file
over `logging.maxFileBytes`. Defaults are:

* `logging.maxFileBytes`: `10485760` bytes (10 MB)
* `logging.maxBackups`: `5`
* Backup compression: gzip

When rotation happens, the active file is moved to `.1.gz`, older backups move
to higher numbers, and backups beyond the retention count are deleted. If
`logging.maxBackups` is `0`, WednesdayAI truncates the active file at rotation
time and keeps no backup.

Other durable diagnostic and audit logs use the same shared file-log controls
through `logging.fileLogs`. These controls do not apply to session transcripts
or conversation storage.

### Console output

Console logs are **TTY-aware** and formatted for readability:

* Subsystem prefixes (e.g. `gateway/channels/whatsapp`)
* Level coloring (info/warn/error)
* Optional compact or JSON mode

Console formatting is controlled by `logging.consoleStyle`.

## Configuring logging

All logging configuration lives under `logging` in `~/.openclaw/openclaw.json`.

```json theme={"dark"}
{
  "logging": {
    "level": "info",
    "file": "/tmp/openclaw/openclaw-YYYY-MM-DD.log",
    "maxFileBytes": 10485760,
    "maxBackups": 5,
    "consoleLevel": "info",
    "consoleStyle": "pretty",
    "redactSensitive": "tools",
    "redactPatterns": ["sk-.*"]
  }
}
```

### Log levels

* `logging.level`: **file logs** (JSONL) level.
* `logging.consoleLevel`: **console** verbosity level.

You can override both via the **`OPENCLAW_LOG_LEVEL`** environment variable (e.g. `OPENCLAW_LOG_LEVEL=debug`). The env var takes precedence over the config file, so you can raise verbosity for a single run without editing `openclaw.json`. You can also pass the global CLI option **`--log-level <level>`** (for example, `openclaw --log-level debug gateway run`), which overrides the environment variable for that command.

`--verbose` only affects console output; it does not change file log levels.

### File-log limits

Use `logging.fileLogs.defaults` to set a global budget for durable file logs
outside sessions and conversations. Use `logging.fileLogs.profiles` when one log
needs a tighter or looser policy.

```json5 theme={"dark"}
{
  logging: {
    fileLogs: {
      defaults: {
        maxFileBytes: 10485760,
        maxBackups: 5,
        compress: "gzip",
        maxQueuedBytes: 1048576,
        mode: "jsonl",
      },
      profiles: {
        "agents.rawStream": {
          maxFileBytes: 1048576,
          maxBackups: 2,
        },
        "hooks.command": {
          maxFileBytes: 2097152,
          maxBackups: 10,
        },
      },
    },
  },
}
```

Common built-in profile ids:

* `primary`: the main Gateway log.
* `cron.run`: cron run JSONL logs.
* `config.audit`: config write audit records.
* `hooks.command`: bundled command logger output.
* `agents.rawStream`: raw-stream diagnostic JSONL.
* `agents.modelPayload`: model payload debug log JSONL (off by default; see below).
* `agents.cacheTrace`: cache-trace debug JSONL.
* `extensions.learningCore`: learning-core JSONL records.
* `extensions.voiceCall.calls`: voice-call legacy call records.

For path-indexed logs such as `cron.run`, `extensions.learningCore`, and
`extensions.voiceCall.calls`, the `file` profile field is ignored so readers and
recovery paths keep using the canonical log path. Tune their size, backup,
compression, queue, and line-retention fields instead.

### Console styles

`logging.consoleStyle`:

* `pretty`: human-friendly, colored, with timestamps.
* `compact`: tighter output (best for long sessions).
* `json`: JSON per line (for log processors).

On `pretty` and `compact` (the defaults), structured metadata passed with a log
call is appended as a dimmed `key=value` suffix after the message, so it reaches
stdout/stderr and journald — for example
`[gateway] recovery incident incidentKey=session-restart:abc kind=restart reasonCode=2`.
Bare-looking strings print unquoted; anything else is JSON-quoted
(`detail="two words"`). `undefined`, functions, and symbols are omitted. An
unserializable value prints `"[unserializable]"`. Hostile metadata degrades to
`[meta unavailable]` rather than throwing. Long values are truncated per key and
across the whole suffix without splitting a UTF-16 surrogate pair.

The `json` console style and the JSONL file log already emitted these fields. On
`json`, each line is one object carrying `time`, `level`, `subsystem`, and
`message` alongside the metadata fields, and those four envelope fields always
win: metadata using the same key cannot overwrite what a log shipper routes on.
Formatting details are also in [Gateway logging](/admin/gateway/logging).

<Warning>
  These fields are now operator-visible on the default styles. Nothing redacts
  them, and the tool-summary redaction below covers tool output, not log metadata.
  Do not put secrets, tokens, or raw credentials in a log call's metadata.
</Warning>

### Redaction

Tool summaries can redact sensitive tokens before they hit the console:

* `logging.redactSensitive`: `off` | `tools` (default: `tools`)
* `logging.redactPatterns`: list of regex strings to override the default set

Redaction affects **console output only** and does not alter file logs.

### Model payload logging (debug escape hatch)

`diagnostics.modelPayloadLog` is an off-by-default, bounded, rotating local capture of model
request payloads, written through `logging.fileLogs` (profile `agents.modelPayload`).

It is a short-lived debugging tool, not a telemetry path. For tokens, cost, latency, and spans use
diagnostics OpenTelemetry / Langfuse (below) — those are the supported, production-grade signals.
Enable payload logging only while reproducing a provider-side payload bug, then turn it off.

Safe by default, in layers:

* **Off** unless `enabled: true`. With no config and no env var, nothing is written and no file is created.
* When `enabled: true`, only **provider-neutral usage summaries** (tokens, latency, provider/model)
  are written, never message content.
* **Raw request bodies require a second explicit flag**, `rawPayloads: true`. Even then content is
  redacted via the shared redactor, and sections are stripped unless `captureContent` opts them in
  (the system prompt stays off even when `captureContent` is `true`).

```json theme={"dark"}
{
  "diagnostics": {
    "modelPayloadLog": {
      "enabled": true,
      "usageSummaries": true,
      "rawPayloads": false,
      "captureContent": false,
      "providers": [],
      "file": "/path/to/logs/model-payload.jsonl",
      "maxQueuedBytes": 1048576
    }
  }
}
```

* `usageSummaries` (default `true`): write usage/cost/latency summary events when enabled.
* `rawPayloads` (default `false`): the separate opt-in for full request bodies.
* `captureContent` (default `false`): `true` for a default set, or an object to gate per type
  (`inputMessages`, `outputMessages`, `toolInputs`, `toolOutputs`, `systemPrompt`, `toolDefinitions`).
* `providers` (default all): optional allowlist; when non-empty, only listed providers are logged.
* `file` (default `$OPENCLAW_STATE_DIR/logs/model-payload.jsonl`): JSONL output path.
* `maxQueuedBytes`: hard cap on in-memory queued bytes before entries are dropped (with a throttled
  warning). Rotation, retention, and compression follow the `agents.modelPayload` profile and the
  `logging.fileLogs` controls above.

Environment overrides (config takes precedence): `OPENCLAW_MODEL_PAYLOAD_LOG` and
`OPENCLAW_MODEL_PAYLOAD_LOG_FILE`. The older `OPENCLAW_ANTHROPIC_PAYLOAD_LOG[_FILE]` names are
deprecated aliases, still honoured with a one-time deprecation warning.

## Diagnostics + OpenTelemetry

Diagnostics are structured, machine-readable events for model runs **and**
message-flow telemetry (webhooks, queueing, session state). They do **not**
replace logs; they exist to feed metrics, traces, and other exporters.

Diagnostics events are emitted in-process, but exporters only attach when
diagnostics + the exporter plugin are enabled.

### OpenTelemetry vs OTLP

* **OpenTelemetry (OTel)**: the data model + SDKs for traces, metrics, and logs.
* **OTLP**: the wire protocol used to export OTel data to a collector/backend.
* OpenClaw exports via **OTLP/HTTP (protobuf)** today.

### Signals exported

* **Metrics**: counters + histograms (token usage, message flow, queueing).
* **Traces**: spans for model usage + webhook/message processing.
* **Logs**: exported over OTLP when `diagnostics.otel.logs` is enabled. Log
  volume can be high; keep `logging.level` and exporter filters in mind.

### Diagnostic event catalog

Model usage:

* `model.usage`: tokens, cost, duration, context, provider/model/channel, session ids.

Message flow:

* `webhook.received`: webhook ingress per channel.
* `webhook.processed`: webhook handled + duration.
* `webhook.error`: webhook handler errors.
* `message.queued`: message enqueued for processing.
* `message.processed`: outcome + duration + optional error.

Queue + session:

* `queue.lane.enqueue`: command queue lane enqueue + depth/active/queued counts.
* `queue.lane.dequeue`: command queue lane dequeue + wait time and depth/active/queued counts.
* `queue.lane.task.completed`: command queue lane task completion with duration and outcome.
* `session.state`: session state transition + reason.
* `session.stuck`: session stuck warning + age.
* `run.attempt`: run retry/attempt metadata.
* `diagnostic.heartbeat`: aggregate counters (webhooks/queue/session).

### Enable diagnostics (no exporter)

Use this if you want diagnostics events available to plugins or custom sinks:

```json theme={"dark"}
{
  "diagnostics": {
    "enabled": true
  }
}
```

### Diagnostics flags (targeted logs)

Use flags to turn on extra, targeted debug logs without raising `logging.level`.
Flags are case-insensitive and support wildcards (e.g. `telegram.*` or `*`).

```json theme={"dark"}
{
  "diagnostics": {
    "flags": ["telegram.http"]
  }
}
```

Env override (one-off):

```text theme={"dark"}
OPENCLAW_DIAGNOSTICS=telegram.http,telegram.payload
```

Notes:

* Flag logs go to the standard log file (same as `logging.file`).
* Output is still redacted according to `logging.redactSensitive`.
* Full guide: [/diagnostics/flags](/diagnostics/flags).

### Export to OpenTelemetry

Diagnostics can be exported via the `diagnostics-otel` plugin (OTLP/HTTP). This
works with any OpenTelemetry collector/backend that accepts OTLP/HTTP.

```json theme={"dark"}
{
  "plugins": {
    "allow": ["diagnostics-otel"],
    "entries": {
      "diagnostics-otel": {
        "enabled": true
      }
    }
  },
  "diagnostics": {
    "enabled": true,
    "otel": {
      "enabled": true,
      "endpoint": "http://otel-collector:4318",
      "protocol": "http/protobuf",
      "serviceName": "openclaw-gateway",
      "traces": true,
      "metrics": true,
      "logs": true,
      "sampleRate": 0.2,
      "flushIntervalMs": 60000
    }
  }
}
```

Notes:

* You can also enable the plugin with `openclaw plugins enable diagnostics-otel`.
* `protocol` currently supports `http/protobuf` only. `grpc` is ignored.
* Metrics include token usage, cost, context size, run duration, and message-flow
  counters/histograms (webhooks, queueing, session state, queue depth/wait, lane
  active/queued/duration pressure).
* Traces/metrics can be toggled with `traces` / `metrics` (default: on). Traces
  include model usage spans plus webhook/message processing spans when enabled.
* Set `headers` when your collector requires auth.
* Environment variables supported: `OTEL_EXPORTER_OTLP_ENDPOINT`,
  `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`, `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`,
  `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`, `OTEL_SERVICE_NAME`,
  `OTEL_EXPORTER_OTLP_PROTOCOL`, and `OTEL_SDK_DISABLED`.

### Exported metrics (names + types)

Model usage:

* `openclaw.tokens` (counter, attrs: `openclaw.token`, `openclaw.channel`,
  `openclaw.provider`, `openclaw.model`)
* `openclaw.cost.usd` (counter, attrs: `openclaw.channel`, `openclaw.provider`,
  `openclaw.model`)
* `openclaw.run.duration_ms` (histogram, attrs: `openclaw.channel`,
  `openclaw.provider`, `openclaw.model`)
* `openclaw.context.tokens` (histogram, attrs: `openclaw.context`,
  `openclaw.channel`, `openclaw.provider`, `openclaw.model`)

Message flow:

* `openclaw.webhook.received` (counter, attrs: `openclaw.channel`,
  `openclaw.webhook`)
* `openclaw.webhook.error` (counter, attrs: `openclaw.channel`,
  `openclaw.webhook`)
* `openclaw.webhook.duration_ms` (histogram, attrs: `openclaw.channel`,
  `openclaw.webhook`)
* `openclaw.message.queued` (counter, attrs: `openclaw.channel`,
  `openclaw.source`)
* `openclaw.message.processed` (counter, attrs: `openclaw.channel`,
  `openclaw.outcome`)
* `openclaw.message.duration_ms` (histogram, attrs: `openclaw.channel`,
  `openclaw.outcome`)

Queues + sessions:

* `openclaw.queue.lane.enqueue` (counter, attrs: `openclaw.lane`)
* `openclaw.queue.lane.dequeue` (counter, attrs: `openclaw.lane`)
* `openclaw.queue.lane.task.completed` (counter, attrs: `openclaw.lane`,
  `openclaw.outcome`)
* `openclaw.queue.lane.active` (histogram, attrs: `openclaw.lane`)
* `openclaw.queue.lane.queued` (histogram, attrs: `openclaw.lane`)
* `openclaw.queue.lane.duration_ms` (histogram, attrs: `openclaw.lane`,
  `openclaw.outcome`)
* `openclaw.queue.depth` (histogram, attrs: `openclaw.lane` or
  `openclaw.channel=heartbeat`)
* `openclaw.queue.wait_ms` (histogram, attrs: `openclaw.lane`)
* `openclaw.session.state` (counter, attrs: `openclaw.state`, `openclaw.reason`)
* `openclaw.session.stuck` (counter, attrs: `openclaw.state`)
* `openclaw.session.stuck_age_ms` (histogram, attrs: `openclaw.state`)
* `openclaw.run.attempt` (counter, attrs: `openclaw.attempt`)

### Exported spans (names + key attributes)

* `openclaw.model.usage`
  * `openclaw.channel`, `openclaw.provider`, `openclaw.model`
  * `openclaw.sessionKey`, `openclaw.sessionId`
  * `openclaw.tokens.*` (input/output/cache\_read/cache\_write/total)
* `openclaw.webhook.processed`
  * `openclaw.channel`, `openclaw.webhook`, `openclaw.chatId`
* `openclaw.webhook.error`
  * `openclaw.channel`, `openclaw.webhook`, `openclaw.chatId`,
    `openclaw.error`
* `openclaw.message.processed`
  * `openclaw.channel`, `openclaw.outcome`, `openclaw.chatId`,
    `openclaw.messageId`, `openclaw.sessionKey`, `openclaw.sessionId`,
    `openclaw.reason`
* `openclaw.session.stuck`
  * `openclaw.state`, `openclaw.ageMs`, `openclaw.queueDepth`,
    `openclaw.sessionKey`, `openclaw.sessionId`

### Sampling + flushing

* Trace sampling: `diagnostics.otel.sampleRate` (0.0–1.0, root spans only).
* Metric export interval: `diagnostics.otel.flushIntervalMs` (min 1000ms).

### Protocol notes

* OTLP/HTTP endpoints can be set via `diagnostics.otel.endpoint` or
  `OTEL_EXPORTER_OTLP_ENDPOINT`.
* Per-signal OTLP/HTTP endpoints can be set with `diagnostics.otel.tracesEndpoint`,
  `diagnostics.otel.metricsEndpoint`, and `diagnostics.otel.logsEndpoint`; matching
  `OTEL_EXPORTER_OTLP_*_ENDPOINT` variables can also override signal routing.
* If the endpoint already contains `/v1/traces` or `/v1/metrics`, it is used as-is.
* If the endpoint already contains `/v1/logs`, it is used as-is for logs.
* `diagnostics.otel.logs` enables OTLP log export for the main logger output.
* At gateway startup, `diagnostics-otel` logs a redacted exporter summary with
  resolved signal URLs, header presence, `OTEL_SDK_DISABLED`, preloaded SDK state,
  and whether the NodeSDK started. Use that line to distinguish "no traces
  configured" from "traces routed somewhere else."

### Log export behavior

* OTLP logs use the same structured records written to `logging.file`.
* Respect `logging.level` (file log level). Console redaction does **not** apply
  to OTLP logs.
* High-volume installs should prefer OTLP collector sampling/filtering.

## Troubleshooting tips

* **Gateway not reachable?** Run `openclaw doctor` first.
* **Logs empty?** Check that the Gateway is running and writing to the file path
  in `logging.file`.
* **Need more detail?** Set `logging.level` to `debug` or `trace` and retry.
