Logging
Logger is what application code uses. It forwards every call to the active logger, which you can
replace for the whole process: change the format, pick levels, ship JSON to an aggregator, or hand records
to Python’s logging, structlog or anything else.
from austial import Logger
class CatsService:
logger = Logger("CatsService")
def create(self, dto):
self.logger.log("Creating a cat")
self.logger.warn("Name is unusually long")
self.logger.error("Could not save", context="CatsRepository") # override the context for one callLevels, least to most severe: verbose, debug, log, warn, error, fatal. The default is the familiar
coloured line:
[Austial] 12345 - 07/11/2026, 10:00:00 AM LOG [CatsService] Creating a catChoosing levels and silencing#
Logger.override_logger(["error", "warn", "fatal"]) # default format, only these levels
Logger.override_logger(False) # silence everything
Logger.override_logger(True) # back to the defaultThe same, at startup:
app = AustialFactory.create(AppModule, logger=["error", "warn"])
ctx = await AustialFactory.create_application_context(AppModule, logger=False)JSON output#
from austial import ConsoleLogger, Logger
Logger.override_logger(ConsoleLogger(json_output=True))Each record becomes one JSON object per line (level, pid, timestamp, context, message), error and
fatal on stderr and the rest on stdout, ready for a log shipper.
A custom logger#
Implement LoggerService: log, error and warn are required; debug, verbose and fatal fall back
to log/error if you don’t define them. For example, route everything through the standard library:
import logging
from austial import LoggerService
class StdlibLogger(LoggerService):
def __init__(self) -> None:
self._logger = logging.getLogger("app")
def log(self, message, context=None):
self._logger.info("[%s] %s", context, message)
def warn(self, message, context=None):
self._logger.warning("[%s] %s", context, message)
def error(self, message, context=None):
self._logger.error("[%s] %s", context, message)
def debug(self, message, context=None):
self._logger.debug("[%s] %s", context, message)
app = AustialFactory.create(AppModule, logger=StdlibLogger())A logger that needs dependency injection (a config service, say) can’t exist yet when the app is created. Buffer the startup output until it does:
app = AustialFactory.create(AppModule, buffer_logs=True)
app.use_logger(app.get(MyInjectedLogger)) # attaches it and replays what was held backbuffer_logs=True holds every record until use_logger() runs or app.init() starts, so the first lines
aren’t printed in the wrong format.
Notes#
Logger.override_logger()is process-wide state: set it once at startup, not per request.Logger.attach_buffer()/Logger.flush()are the two halves of buffering if you want them separately.- Library code (including Austial’s own startup lines) logs through the same
Logger, so one setting controls everything.