Skip to content

Application logging

std.logging configures Python's standard logging pipeline once from the application composition root. It provides environment defaults, a readable console formatter, newline-delimited JSON, and a typed custom-format path. It does not add a logging dependency.

Environment presets

Pass the application's environment to configure_logging. format: auto and an unset level select these defaults:

Environment Default level Default format Color
development (dev, local) DEBUG Human-readable console Automatically on for a terminal
test WARNING Human-readable console Off unless explicitly enabled
staging (stage) INFO JSON Off
production (prod) INFO JSON Off

Staging uses the production JSON shape so deployments can exercise the same log collection path before production. Set level or format to override a profile independently. LoggingEnvironment validates canonical environment names and accepts the aliases shown above.

Configure from YAML

Keep settings in the application's single YAML file and let std.config resolve the environment placeholders:

application:
  name: ${APP_NAME:-Example API}
  environment: ${APP_ENV:-development}

logging:
  level: ${LOG_LEVEL:-null}
  format: ${LOG_FORMAT:-auto}
  stream: ${LOG_STREAM:-stdout}
  color: ${LOG_COLOR:-auto}
  include_source: ${LOG_INCLUDE_SOURCE:-false}
  date_format: ${LOG_DATE_FORMAT:-null}
  custom_format: ${LOG_CUSTOM_FORMAT:-null}
  custom_style: ${LOG_CUSTOM_STYLE:-%}
  logger_levels: {}

The application settings aggregate references LoggingConfig; main.py is the only place that creates settings and configures logging:

from app.core.settings import Settings
from std.logging import configure_logging


def main() -> None:
    settings = Settings()
    configure_logging(
        settings.logging,
        environment=settings.application.environment,
        service_name=settings.application.name,
    )
    # Construct services and inject dependencies after logging starts.


if __name__ == "__main__":
    main()

The FastAPI app template demonstrates this startup path with injected services and a managed lifespan.

Formats and overrides

LoggingConfig.format accepts:

Value Behavior
auto Select console for development/tests and JSON for staging/production.
console Human-readable UTC timestamp, level, logger, and message.
json One JSON object per line, with UTC timestamp, level, logger, message, environment, optional service/source, exception details, and extra fields.
custom Use custom_format with the %, {, or $ style supported by logging.Formatter.

Other options are:

Field Purpose
level Override the environment's default with DEBUG, INFO, WARNING, ERROR, or CRITICAL. None keeps the profile default.
stream Write to stdout (default) or stderr.
color auto, always, or never; applies to console output.
include_source Add filename and line to console output, or file, line, and function fields to JSON.
date_format Override the console/custom timestamp format. Without it, timestamps use ISO 8601 UTC.
custom_format Required for format: custom; supports the standard formatter fields.
custom_style The formatter style: %, {, or $.
logger_levels Optional mapping of named logger paths to severity levels. It does not disable other loggers.

JSON records include arbitrary logging extra values as top-level fields. Exception records use exception.type, exception.message, and exception.stacktrace; source fields are opt-in. Keep passwords, tokens, personal data, and other secrets out of messages, exception text, and extra values. This formatter does not attempt to infer or redact secrets.

The default destination is stdout so a hosting runtime can collect and route the event stream. Set stream: stderr when the application uses stdout for another protocol. Configure file rotation or external transport in the deployment's log collector rather than adding hidden file writes to application startup, following the Twelve-Factor logs guidance.

Direct use

from std.logging import LoggingConfig, configure_logging

configure_logging(
    LoggingConfig(level="INFO", format="json", include_source=True),
    environment="production",
    service_name="billing-api",
)

Application modules should use logging.getLogger(__name__) and log with standard levels. Add request or trace correlation fields with extra, for example logger.info("request complete", extra={"request_id": request_id}). JSON keeps those fields structured so a collector can index them. The field shape follows common logging concepts and can be mapped to the OpenTelemetry log data model.

Python's logging API provides the levels, logger hierarchy, handlers, and formatters used here. Django's logging guide is another example of keeping logging policy in application configuration. This module uses a validated subset so YAML remains declarative and cannot load arbitrary formatter factories.