Skip to content

Logging

Logging is built into the core as a wrapper over the standard library logging, plus an always-on middleware that times requests and masks sensitive data.

What gets logged

Each request produces a line like:

[INFO]  POST /v1/user/role 201 3ms id=8f2c… payload={'name': 'Ada', 'password': '***'}
[ERROR] GET /v1/user/0 -> 500 5ms id=… payload=… <traceback>
  • method, path, status, duration, and a request id;
  • the request payload with sensitive keys masked (password, token, authorization, secret, api_key, …) before anything is written — matching is by substring, case/separator-insensitive, so old_password, X-Api-Key, and refresh_token are masked too, not just an exact field name;
  • on error, the full traceback.

Why masking is at the logger layer

The logging middleware sees the raw inbound JSON — before any encryption or storage. Masking there is the only place that reliably keeps secrets out of the logs. Encryption-at-rest in the DB doesn't help the log stream.

Request IDs

Every request gets an X-Request-ID (an inbound one is honoured), echoed on the response and included in every log line — so you can trace one request end to end. Behind a proxy, forward the header.

Colored dev output

When stderr is a TTY, the level prefix is colored for readability. When output is redirected (files, aggregators), plain text is used automatically.

Using the logger yourself

from endocore import get_logger
log = get_logger()          # the configured "endocore" logger

log.info("processed order %s", order_id)

Mask a structure manually:

from endocore.core.logging import mask
mask({"password": "x", "name": "Ada"})     # {"password": "***", "name": "Ada"}

Adjusting levels / handlers

get_logger() returns a standard logging.Logger, so configure it as usual:

import logging
logging.getLogger("endocore").setLevel(logging.WARNING)