A modern, friendly Python logging library with sensible defaults, colored
console output, rotating files, structured JSON, context binding, and
decorators. Built on top of the standard logging module so it composes
naturally with the rest of the Python ecosystem.
- Why logthisx
- Installation
- Quickstart
- Configuration
- Console logging
- File logging
- JSON logging
- Context binding
- Decorators
- Custom levels
- Exceptions
- Redaction
- CLI
- Migration from 0.1.x
- Development
- License
- Looks good in your terminal out of the box, without forcing you to know
the
logging.configDSL. - Plays nicely with the standard library: every
logthisxlogger is alogging.Logger, so third-party libraries that already uselogginginherit your configuration. - No mandatory third-party dependency.
richsupport is available as an optional extra. - Structured logging built in: JSON formatter with a stable schema for Docker, CI, log aggregators and observability tools.
- Practical features that are tedious to wire up by hand: rotation, context binding, decorators, redaction.
pip install logthisxWith the optional rich extra for fancier console output:
pip install "logthisx[rich]"logthisx supports Python 3.9 to 3.13.
from logthisx import configure, logger
configure(level="INFO", console=True, file="logs/app.log")
logger.info("Application started")
logger.success("Operation completed")
logger.warning("Something looks wrong")
logger.error("Something failed")
logger.debug("Debug value", extra={"value": 42})You can also retrieve named loggers:
from logthisx import configure, get_logger
configure(level="DEBUG", console=True)
log = get_logger("my.app")
log.info("Ready")The package is side-effect free on import: it does not touch the root
logger unless you ask for it explicitly (logger_name="root").
configure() is the single entry point that wires handlers and formatters
together. It is idempotent: calling it again replaces the handlers it
previously installed, so duplicated log lines are not a concern.
from logthisx import configure
configure(
level="INFO",
console=True,
console_json=False,
detailed=False,
show_time=True,
time_format="%Y-%m-%d %H:%M:%S",
use_color=None,
file="logs/app.log",
file_json=False,
max_bytes=5 * 1024 * 1024,
backup_count=5,
redact=True,
custom_levels=False,
logger_name="logthisx",
propagate=False,
)Alternatively, build a Config and pass it in:
from logthisx import Config, configure
cfg = Config(level="DEBUG", file="logs/app.log", file_json=True)
configure(config=cfg)Or read it from the environment:
from logthisx import Config, configure
configure(config=Config.from_env())Recognized environment variables (default prefix LOGTHISX_):
| Variable | Description |
|---|---|
LOGTHISX_LEVEL |
Log level (DEBUG, INFO, WARNING, ...) |
LOGTHISX_CONSOLE |
Enable console handler (1/0) |
LOGTHISX_JSON |
Use JSON console output (1/0) |
LOGTHISX_DETAILED |
Include module/function/line in console (1/0) |
LOGTHISX_FILE |
Path to the log file |
LOGTHISX_MAX_BYTES |
Rotation threshold in bytes |
LOGTHISX_BACKUP_COUNT |
Number of rotated files to keep |
LOGTHISX_REDACT |
Enable redaction of sensitive keys (1/0) |
The default console formatter is a colorized, compact one-liner. Colors are
auto-detected based on whether stderr is a TTY; set use_color=True/False
to force the behavior, or set NO_COLOR=1 / FORCE_COLOR=1 in the
environment.
Enable the detailed mode to include module.function:line:
configure(level="DEBUG", console=True, detailed=True)configure(file="logs/app.log") installs a RotatingFileHandler that:
- Creates the parent directory if it does not exist.
- Uses UTF-8 encoding by default.
- Rotates the file once it reaches
max_bytes(default 5 MiB). - Keeps
backup_counthistorical files (default 5). - Returns a no-op handler and prints a warning if the file cannot be opened, so logging never crashes the host application.
Set console_json=True or file_json=True to switch to the JSON formatter.
The schema is stable across releases and looks like:
{
"timestamp": "2026-01-02T15:04:05.123456+00:00",
"level": "INFO",
"logger": "my.app",
"module": "api",
"function": "handle_request",
"line": 42,
"message": "Request received",
"extra": {"request_id": "abc"},
"exception": {
"type": "ValueError",
"message": "boom",
"traceback": "..."
}
}extra is always present (possibly empty). exception is only present when
the record carries exception information. Timestamps are ISO 8601 with
explicit UTC offset.
Attach structured context to every record without repeating yourself:
from logthisx import get_logger
log = get_logger("api").bind(request_id="abc123", user_id=42)
log.info("Request received")
log.info("Cache hit", extra={"cache_key": "users:42"})bind() returns a new logger; the parent is not mutated. Chain calls to
extend the context, and use unbind("user_id") to drop a key.
The context shows up in console output, in files and in JSON payloads.
from logthisx.decorators import log_call, log_errors, log_time
@log_call(level="DEBUG", include_args=True)
def add(a, b):
return a + b
@log_time(threshold_ms=50.0)
def slow_path():
...
@log_errors()
def risky():
raise RuntimeError("boom")All three decorators support both def and async def functions. They are
cheap when the relevant level is disabled (a single isEnabledFor check).
logthisx ships with three extra levels:
| Constant | Value | Default name |
|---|---|---|
SUCCESS |
25 | SUCCESS |
ALERT |
35 | ALERT |
SPECIAL |
45 | SPECIAL |
They are registered automatically when configure(custom_levels=True) is
called, and exposed as methods (logger.success, logger.alert,
logger.special).
Optional short aliases (II, DD, SS, WW, AA, EE, AZ, CC) can
be turned on with custom_level_aliases=True. They are off by default to
avoid polluting the global namespace for users who prefer standard names.
Use logger.exception("message") from inside an except: block. The
traceback is rendered cleanly in the console and stored verbatim in files
and inside the exception.traceback field of JSON records.
try:
risky()
except Exception:
logger.exception("risky failed")extra dictionaries are scanned for sensitive keys (password, token,
api_key, secret, authorization, ...) and their values are replaced by
*** before being written. Set configure(redact=False) to disable, or
pass a custom set of keys to the formatters directly if you need a tighter
or looser policy.
The redaction does not inspect the free-form message text. Replacing
arbitrary substrings in messages is unreliable and gives a false sense of
security. Pass secrets through extra so the redaction can do its job.
logthisx --version
logthisx demo # Emit one record per level so you can preview output
logthisx demo --json # Same, but in JSON
logthisx doctor # End-to-end self-check (console, file, rotation, JSON)logthisx demo is also handy for verifying that your terminal renders the
colors correctly.
logthisx 0.1 exposed four module-level functions:
from logthis import log_info, log_warn, log_error, enable_file_logging
enable_file_logging("logs/app.log")
log_info("hello")These keep working in 0.2.0 but emit DeprecationWarning. The recommended
new API is:
from logthisx import configure, logger
configure(level="INFO", console=True, file="logs/app.log")
logger.info("hello")
logger.warning("careful")
logger.error("broken")See MIGRATION.md for the full guide.
git clone https://github.com/laxyny/logthis.git
cd logthis
python -m pip install -e ".[dev]"
python -m ruff check src tests
python -m ruff format --check src tests
python -m pytest
python -m buildReleased under the MIT License.