Skip to main content
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.
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.
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.

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:
or, in JSON mode:
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:
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.
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.