Quick Start#
Installation#
pip install thinlog
Minimal example#
The simplest way to use Thinlog is with a root-level config:
[logging]
root = {level = "DEBUG"}
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"])
logger.info("Hello from Thinlog!")
Console logging with StreamHandler#
A slightly richer setup that logs to stderr:
[logging]
root = {level = "DEBUG", handlers = ["stream"]}
[logging.handlers]
stream = {class = "logging.StreamHandler", level = "DEBUG", stream = "ext://sys.stderr"}
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"])
logger.info("Logged to stderr")
logger.debug("Debug details", request_id="abc-123")
Rich handler#
For pretty terminal output using Rich:
[logging]
root = {level = "DEBUG", handlers = ["rich"]}
[logging.handlers]
rich = {"()" = "rich.logging.RichHandler", level = "DEBUG", rich_tracebacks = true, tracebacks_show_locals = true}
JSON logging with QueueHandler#
For structured JSON output in production, use JsonFormatter
behind a QueueHandler for thread-safe, non-blocking delivery:
[logging]
root = {level = "DEBUG", handlers = ["queue"]}
[logging.formatters]
json = {"()" = "thinlog.formatters.json.JsonFormatter", show_locals = true}
msg = {"()" = "thinlog.formatters.msg.MsgFormatter"}
[logging.handlers]
stream = {class = "logging.StreamHandler", level = "DEBUG", stream = "ext://sys.stderr", formatter = "msg"}
queue = {class = "logging.handlers.QueueHandler", handlers = ["stream"], formatter = "json", respect_handler_level = true}
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"])
logger.warning("Something happened", user_id=42)
The QueueHandler offloads formatting and I/O to a background thread, keeping
the calling thread fast. configure_logging() automatically
detects and starts the listener, and registers an atexit() handler to
stop it on interpreter exit.
Keyword arguments as extra fields#
KeywordFriendlyLogger (the logger returned by
configure_logging()) lets you pass arbitrary keyword
arguments that become extra fields on the log record:
logger.info("User signed in", user_id=42, ip="10.0.0.1")
# The record now has record.user_id = 42 and record.ip = "10.0.0.1"
This is especially useful with JsonFormatter,
which serialises the entire record (including extra fields) as JSON.
Next steps#
Configuration – wildcard loggers, merge, and multi-process patterns.
Filters – whitelist, blocklist, and attribute assignment.
Handlers – HTTP, Telegram, and context-print handlers.
Formatters – JSON, message-only, and Telegram formatters.