Logging¶
Patera logs with Loguru. Use self.app.logger in controllers
and services (or app.logger).
self.app.logger.info("Note created")
self.app.logger.bind(note_id=note.id).warning("Note title truncated")
The default sink¶
The default console sink is configured with DEFAULT_LOGGER, a DefaultLoggerConfig model.
Fields you leave out keep their defaults, and unknown fields are rejected:
from patera.logging import DefaultLoggerConfig
class Config(BaseConfig):
...
DEFAULT_LOGGER: DefaultLoggerConfig = DefaultLoggerConfig(
SINK="STDERR", # STDERR, STDOUT, NULL or a file path
LEVEL="INFO",
ENQUEUE=True,
COLORIZE=True,
)
Fields can also be set from the environment, for example PATERA_DEFAULT_LOGGER__LEVEL=INFO.
Log files¶
Every record at LOG_FILES.LEVEL or above is also written to a JSON Lines file, one JSON object
per line with time, level, message, logger_name, name, file, line, function,
process, thread, and, when present, exception (the formatted traceback) and extra (values
bound with logger.bind(...)). The admin dashboard reads its log view
from these files.
from patera.logging import LogFilesConfig
class Config(BaseConfig):
...
LOG_FILES: LogFilesConfig = LogFilesConfig(
PATH="logs/patera.jsonl", # relative to the application package; must end with .jsonl
LEVEL="INFO",
ROTATION="10 MB", # size, time of day ("00:00") or interval ("1 day"); None = never
FILE_COUNT=5, # files kept, including the active one
)
| Field | Default | Description |
|---|---|---|
ENABLED |
True |
Write log files. |
PATH |
logs/patera.jsonl |
Active file. Rotated files are renamed with their rotation time, e.g. patera.2026-10-09_13-25-00_123456.jsonl. |
LEVEL |
DEBUG |
Minimum level written. |
ROTATION |
10 MB |
When the active file is rotated. |
FILE_COUNT |
5 |
Number of files kept; older rotated files are deleted. |
ENQUEUE |
True |
Write from a background queue. |
READ_LIMIT |
10000 |
Maximum number of newest records returned by app.log_files.read(). |
app.log_files.read(limit) (or await app.log_files.read_async(limit)) returns the newest
records, oldest first.
Several workers
Rotation is not coordinated between processes. With several server workers appending to one
file, set ROTATION=None and rotate with an external tool, or run a single worker.
Custom sinks¶
Additional sinks are classes in a logging / loggers folder (file name containing logger,
log_sink or logging):
# app/loggers/file_logger.py
from patera.logging import LoggerBase, logger
@logger("File logger")
class FileLogger(LoggerBase):
def get_filter(self):
return lambda record: record["level"].no >= 30 # WARNING and above
Its settings are read from the config field named after the class:
class Config(BaseConfig):
...
FILE_LOGGER: dict[str, Any] = {
"SINK": "logs/app.log", # relative to the application package
"LEVEL": "WARNING",
"ROTATION": "5 MB",
"RETENTION": "10 days",
"COMPRESSION": "zip",
}
Override the sink getters as needed; configs_model selects the settings model. Loguru is
process-global, so applications in the same process share its sinks.
Note
Built-in request logging includes the query string. Redact at the logging boundary or avoid sensitive URL parameters.
Request observers¶
For HTTP metrics, register an observer, typically from a service listed in app_extensions:
from patera import RequestRecord, Service
class RequestMetrics(Service):
def init(self):
self.app.add_request_observer(self.observe)
def observe(self, record: RequestRecord):
if record.duration > 1:
self.app.logger.warning(f"Slow request: {record.method} {record.path}")
- Observers see static requests and routing failures that middleware misses, and run after the response is sent.
- Records include the method, path, matched route (or
None), sent status, start time and duration in seconds. - Keep callbacks quick. Failures are logged and skipped, and an observer cannot change the response.