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:

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