Skip to content

Route task logs to stdout through the worker log handler - #58

Merged
codingjoe merged 2 commits into
mainfrom
codingjoe-task-log-routing
Sep 17, 2026
Merged

codingjoe merged 2 commits into
mainfrom
codingjoe-task-log-routing

Conversation

@codingjoe

Copy link
Copy Markdown
Owner

A logger used inside a task never printed. The JSON handler was attached to the multiprocessing logger, so records from any other logger never reached a handler: logger.info(...) in a task was dropped outright, and logger.error(...) fell back to Python's lastResort handler, printing unformatted text to stderr and ignoring --log-format.

Worker processes now own their logging configuration. configure_logging() puts the shared handler on the root logger at INFO, writing to stdout, and replaces the handlers Django installs during django.setup() plus any application handler. It runs in TaskExecutor.run() for the supervisor and in WorkerProcess.run() for each worker, after django.setup(), so Django cannot reinstall its console handler afterwards.

The result: records from tasks, threadmill, and Django all pass through the configured formatter (single-line JSON by default, --log-format to override) on stdout, with no duplicated or unformatted lines on stderr.

Worth a look during review: because the worker owns logging, handlers configured in LOGGING are replaced inside the worker command. The alternative kept application handlers and duplicated every django.* record across two formats.

Covered by unit tests for configure_logging() (handler placement, foreign handler replacement, placeholder loggers) and an end-to-end test that runs a logging task and asserts its JSON record lands on stdout rather than stderr.

codingjoe and others added 2 commits September 17, 2026 13:41
The JSON handler was attached to the `multiprocessing` logger only, so
records of any other logger never reached it: an `info()` call inside a
task was dropped, and an `error()` call fell back to Python's lastResort
handler, printing unformatted text to stderr instead of honoring
`--log-format`.

Configure logging per process instead: the handler goes on the root
logger at INFO, replacing handlers Django installs in `django.setup()`
and any application handler, and it writes to stdout. Every record from
tasks, threadmill, and Django now uses the configured formatter.
Python 3.14 defaults to forkserver on Linux, and a worker forked from the
long-lived forkserver inherits that server's stdout rather than the file
descriptor the `capfd` fixture swaps in for the test. The records of the
worker never reached `capfd`, so `test_run__routes_task_logs_to_stdout`
failed on CI while it passed on macOS, where spawn is the default.

Run the executor with the spawn start method for this test and restore the
original method afterwards, the same way the neighbouring spawned-worker
test does. The assertions are unchanged, and the test still fails with the
handler attached to the multiprocessing logger only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@codingjoe
codingjoe merged commit 0eee0b8 into main Sep 17, 2026
4 checks passed
@codingjoe
codingjoe deleted the codingjoe-task-log-routing branch September 17, 2026 12:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant