Configuration#
Thinlog builds on Python’s logging.config.dictConfig(). You write your
logging configuration as a TOML file (or a dict) and pass it to
configure_logging(), which applies the config, starts any
QueueHandler listener, and returns a ready-to-use
logger. Cleanup (stopping listeners, flushing handlers) is handled
automatically via an atexit() handler.
LoggingSettings#
LoggingSettings is a typed dataclass whose fields mirror the
keys accepted by logging.config.dictConfig():
See LoggingSettings in the API Reference.
You can pass either a LoggingSettings instance or a plain
dict to configure_logging() — the dict is used directly while
the dataclass is converted via dataclasses.asdict().
TOML configuration structure#
Thinlog expects a [logging] table in your TOML config:
[logging]
root = {level = "INFO", handlers = ["queue"]}
[logging.formatters]
json = {"()" = "thinlog.formatters.json.JsonFormatter", show_locals = false}
msg = {"()" = "thinlog.formatters.msg.MsgFormatter"}
[logging.handlers]
stream = {class = "logging.StreamHandler", level = "INFO", stream = "ext://sys.stderr", formatter = "msg"}
queue = {class = "logging.handlers.QueueHandler", handlers = ["stream"], formatter = "json", respect_handler_level = true}
Load and apply it:
import tomllib
from pathlib import Path
from thinlog import configure_logging
config = tomllib.loads(Path("config.toml").read_text())
logger = configure_logging("myapp", config["logging"])
Wildcard logger ("*")#
When your application has many loggers (one per module, one per library, etc.), configuring each one individually is tedious. Thinlog’s wildcard logger solves this:
[logging.loggers]
"*" = {level = "INFO", handlers = ["queue"]}
When configure_logging() encounters the "*" key, it copies
that configuration to every logger in the more_loggers list and every logger
registered in RegisteredLoggers:
from thinlog import get_logger, configure_logging
# Pre-register loggers across your codebase
auth_logger = get_logger("auth", {"component": "auth"})
db_logger = get_logger("db", {"component": "db"})
# Later, at startup:
logger = configure_logging(
"myapp",
config["logging"],
include_registered_loggers=True,
include_default_logger=True,
)
# Now "auth", "db", and "myapp" all have the wildcard config applied.
Merge ("merge": true)#
Sometimes a specific logger needs the wildcard config plus some extras.
Set "merge": true on the specific logger to combine rather than replace:
[logging.loggers]
"*" = {level = "INFO", handlers = ["queue"]}
[logging.loggers.trace_log]
merge = true
handlers = ["trace_handler"]
With merge, trace_log inherits level = "INFO" and handlers = ["queue"]
from the wildcard, and "trace_handler" is appended to the handlers list.
Dicts are deep-merged; lists are extended; scalars are overwritten.
Multi-process applications#
In multi-process setups (e.g., gunicorn, uvicorn workers, Celery),
configure logging on each worker start, not just in the main process.
Use a QueueHandler to offload I/O to a background
thread within each worker:
[logging.handlers]
stream = {class = "logging.StreamHandler", level = "INFO", stream = "ext://sys.stderr", formatter = "msg"}
queue = {class = "logging.handlers.QueueHandler", handlers = ["stream"], formatter = "json", respect_handler_level = true}
configure_logging() automatically detects the QueueHandler,
starts its listener, and registers an atexit() handler to stop it on
interpreter exit.
Separate loggers per component#
A recommended pattern for microservices:
One application logger (
"myapp") for business logic.One library logger (
"mylib") per internal library.Use the wildcard so all loggers share a common base config.
Use
JsonHTTPHandlerto group logs by category via HTTP headers — theAssignerFiltercan sethandlers_contextto add per-logger headers (e.g.,cat: "auth").
# In your library
from thinlog import get_logger
logger = get_logger("mylib.auth", {"component": "auth"})
logger.info("Token validated", user_id=42)
Full example#
A production-ready configuration with formatters, handlers, filters, and a queue:
[logging]
root = {level = "INFO", handlers = ["queue"]}
[logging.formatters]
json = {"()" = "thinlog.formatters.json.JsonFormatter", show_locals = false}
msg = {"()" = "thinlog.formatters.msg.MsgFormatter"}
telegram = {"()" = "thinlog.formatters.telegram.TelegramFormatter"}
[logging.handlers]
stream = {class = "logging.StreamHandler", level = "INFO", stream = "ext://sys.stderr", formatter = "msg"}
http = {"()" = "thinlog.handlers.json_http.JsonHTTPHandler", level = "WARNING", formatter = "msg", settings = {url = "https://example.com/logs", headers = {token = "USERNAME:PASSWORD", cat = "context.base.microservice"}}}
telegram = {"()" = "thinlog.handlers.telegram.TelegramHandler", level = "WARNING", formatter = "telegram", settings = {token = "BOT_TOKEN", chat_id = "-100CHATID", topic_id = 12345}}
queue = {class = "logging.handlers.QueueHandler", handlers = ["stream", "telegram", "http"], formatter = "json", respect_handler_level = true}
import tomllib
from pathlib import Path
from thinlog import configure_logging
config = tomllib.loads(Path("dev.toml").read_text())
logger = configure_logging(
"myapp",
config["logging"],
extra={"component": "api"},
include_default_logger=True,
include_registered_loggers=True,
)
logger.info("Service started")
logger.error("Something broke", exc_info=True)
See Filters for adding filters to this configuration.
API reference#
See configure_logging() in the API Reference.