Logs
Flux's logging surface — stdout target, log levels and format, the lack of a JSON formatter in 0.56.0, and how to ship logs for incident response.
Flux uses Python’s stdlib logging module. Flux 0.56.0 has no custom log layer and no JSON formatter. Everything goes to stdout; ship it from there to whatever pipeline your platform expects.
Where logs go
The framework attaches a single StreamHandler to sys.stdout for the flux logger root (flux/utils.py:325):
console_handler = logging.StreamHandler(sys.stdout)
This matters. Container runtimes capture both streams, but many shipping pipelines (Fluent Bit, Vector, Datadog agent) treat stderr as the error channel. With Flux, errors and info share one stream — distinguish them by levelname in the formatted line, not by file descriptor.
If you run Flux under a supervisor that swallows stdout, you will see no logs. Run the Python process as PID 1, or use tini to forward signals without redirecting streams.
Levels
log_level accepts the standard stdlib values: DEBUG, INFO, WARNING, ERROR, CRITICAL. The default is INFO (flux/config.py:294). Set it via TOML or environment:
# flux.toml
log_level = "INFO"
FLUX_LOG_LEVEL=DEBUG poetry run flux start server
The level is applied to the flux root logger. Component loggers (flux.server, flux.worker, flux.security, flux.scheduler, …) inherit it through Python’s logging hierarchy (flux/utils.py:332-355). There is no per-logger setting — if you need flux.worker at DEBUG while keeping flux.server at INFO, call logging.getLogger("flux.worker").setLevel("DEBUG") after import, or use a logging.config.dictConfig at startup.
Format
log_format is a stdlib logging.Formatter format string, not a switch between text and JSON. The default is:
%(asctime)s - %(name)s - %(levelname)s - %(message)s
You can change the layout but cannot ask Flux for structured output. There is no JSON formatter in 0.56.0. The observability subpackage’s only addition to logging is an OTel filter that stamps otelTraceID and otelSpanID onto each record (flux/observability/logging.py:8-22).
If you need JSON for a SIEM, two options work:
- Sidecar reformatter. Ship Flux’s plain-text lines to Fluent Bit or Vector and apply a regex → JSON transform there.
- Override the formatter at boot. Wrap
flux.utils.configure_loggingwith a startup that swaps inpython-json-logger. Brittle across upgrades — prefer the sidecar.
Trace correlation
When observability is enabled (FLUX_OBSERVABILITY__ENABLED=true), Flux attaches OTelTraceLogFilter to the flux logger. Every log record then carries otelTraceID and otelSpanID attributes (flux/observability/logging.py). Add %(otelTraceID)s to your log_format to thread logs together with traces:
log_format = "%(asctime)s [%(otelTraceID)s] %(name)s - %(levelname)s - %(message)s"
When observability is disabled, those attributes are absent and the format string will raise — gate the format on the observability flag, or use one that ignores trace fields.
There is no built-in HTTP request-ID middleware. For request correlation, terminate requests behind a proxy that injects X-Request-ID and have your log pipeline join on it; the metrics middleware (flux/observability/middleware.py) records HTTP latency but does not stamp request IDs onto log records.
Access logs
Uvicorn’s access log is off in production. The server boot path passes log_level="warning" and access_log=False to uvicorn.Config (flux/server.py:369-375), so you do not get a line per HTTP request unless you change it. Enable it temporarily for incident response by patching the server entrypoint or fronting Flux with a reverse proxy that logs access. The MCP server takes the opposite default — it enables access logs when log_level is DEBUG (flux/mcp_server.py:42).
Security events
Sign-ins, API-key creation, and permission denials are written to the same stdout stream as everything else — there is no separate audit table or audit log file. Look for records on the flux.security logger and forward your container stdout to the SIEM that owns audit retention.
Failure modes
- Logs missing in Kubernetes. Usually the container is not capturing PID 1’s stdout. Run Flux directly (
CMD ["poetry", "run", "flux", "start", "server"]), not under a shell wrapper that buffers. - Secrets leaked through
print(). Tasks thatprint()a secret send it to stdout, where it lands in your log pipeline. Treat the stream as untrusted and run a redaction pass at ingest. - Downstream tooling expects stderr. Flux writes everything to stdout. If your aggregator was configured under the assumption that errors come from stderr, adjust the source rather than the framework.