Quick Start#

Installation#

pip install thinlog

Minimal example#

The simplest way to use Thinlog is with a root-level config:

config.toml#
[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:

config.toml#
[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:

config.toml#
[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:

config.toml#
[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.