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

# Logging

> Configure fymo's logging section in fymo.yml and see what gets logged automatically for requests and background jobs.

Fymo installs one handler on Python's root logger at startup. That single handler catches everything: fymo's own request and job logs, your app's `logging.getLogger(...)` calls, and library logs like Procrastinate's worker output. They all land in one place, in one format, with no per-module setup needed.

## Configuring fymo.yml

Everything is controlled by the `logging:` section. The section is optional; every key has a default.

```yaml theme={null}
logging:
  destination: file       # terminal (default) or file
  file: log/fymo.log      # required when destination is file
  level: info              # debug | info | warning | error (default info)
  format: json              # text | json (default: text in dev, json in prod)
```

| Key           | Values                                | Default                       |
| ------------- | ------------------------------------- | ----------------------------- |
| `destination` | `terminal`, `file`                    | `terminal`                    |
| `file`        | path, required if `destination: file` | none                          |
| `level`       | `debug`, `info`, `warning`, `error`   | `info`                        |
| `format`      | `text`, `json`                        | `text` in dev, `json` in prod |

A relative `file` path resolves against the project root. Validation is fail-fast: an unrecognized value raises a `ValueError` that names the offending key, right at startup. That beats silently falling back to a default and logging somewhere you didn't expect.

<Note>
  `configure()` is idempotent. Calling it again, such as across a test session or after a config reload, swaps out only the handler fymo itself installed. Handlers attached by anything else, including pytest's `caplog`, are left alone.
</Note>

## What gets captured

Because the handler sits on the root logger, three sources flow through it:

* Fymo's own access and job logs.
* Any logger your app code creates with `logging.getLogger(__name__)`.
* Library logs, such as Procrastinate's worker output in the jobs process.

The root logger's level is set to match `logging.level`. Otherwise, the stdlib default of `WARNING` would filter out your app's `INFO` lines before the handler even saw them.

In text mode, app and library records print as `LEVEL logger: message`; fymo's own lines print pre-formatted (see below). In JSON mode, they become `{"logger": ..., "level": ..., "message": ...}` objects instead. An `exc_info` key gets added whenever the record carries a traceback.

One more source rides alongside: the Node sidecar's stderr is captured and forwarded line by line with a `[sidecar]` prefix. A `console.log` inside a component during server-side rendering lands there, so in `fymo dev` you'll see it as `[sidecar] your message` in the terminal instead of it vanishing (or, as in versions before 0.18.1, hanging the request).

## Request logs

Every completed request gets one log line, at the `INFO` level:

```
GET /dashboard 200 4.2ms
```

or, in JSON mode:

```json theme={null}
{"method": "GET", "path": "/dashboard", "status": 200, "duration_ms": 4.2}
```

Only the method, path, status, and duration get logged. Cookies, request bodies, and auth headers are never touched here, by design.

## Job lifecycle logs

Every job submitted through a `JobProvider` runs through a shared lifecycle wrapper. Both the threaded runner and the Procrastinate worker log the same three states:

| Status      | Level   | When                                               |
| ----------- | ------- | -------------------------------------------------- |
| `started`   | `debug` | before the job function runs                       |
| `succeeded` | `info`  | after it returns, with `duration_ms`               |
| `failed`    | `error` | on exception, with `duration_ms` and the traceback |

```json theme={null}
{"job": "send_email", "status": "succeeded", "duration_ms": 812.4}
```

As with request logs, job arguments are never logged, since they can carry PII or secrets. That also means the started line (see the table above) only shows up when you set `level: debug`. At the default info level, you'll only see the succeeded and failed lines.

<Warning>
  Procrastinate's own logger echoes each job's full call string, arguments included, at `INFO`. To keep that from leaking through the shared root handler, `run_worker()` caps the procrastinate logger to `WARNING` by default. If you explicitly call `logging.getLogger("procrastinate").setLevel(logging.INFO)` yourself before starting the worker, that choice is respected and left alone.
</Warning>
