get_logger()
|
v
+---------------------------+
| LoggingRich |
+---------------------------+
| .info .success .warning | --> Rich-formatted console
| .error .debug .rule | with auto file:line prefix
| .track .print .info_json|
+---------------------------+
plus, from the same package:
@lru_cache + DisableableLRUCache --> cached calls,
with a disable hook for tests
path_join / path_exists / path_open --> local + s3:// transparent
get_elapsed_time(seconds) --> "01d : 01h : 12m : 03s"
sha256sum(path) / get_cache_dir() --> stable on-disk caching
klogr is what you reach for when stdlib logging.basicConfig doesn't cut it and print() feels gross. It wraps Python's logging module with rich.logging.RichHandler pre-configured, adds stacklevel awareness so every line shows where it came from, and ships a handful of helpers (caching, paths, timing) that show up in every ML/data project.
- Drop-in
get_logger()— module-scoped, cached, zero setup. Nologging.basicConfig, no handler wiring. - Stacklevel-aware — every log line auto-prefixes with the caller's
file:line. Stop hunting for which module shouted what. logger.track()— wrapsrich.progress.track()and works insideThreadPoolExecutor/ProcessPoolExecutorwithout going silent.logger.print()/logger.info_json()— pretty-print Pydantic models, dicts, JSON without piping throughprint(json.dumps(..., indent=2)).@lru_cachewith a disable hook — flip an env var to bypass caching globally (handy for tests).- Path helpers that handle S3 —
path_join,path_exists,path_mkdir, etc. work whether you pass/tmp/fooors3://bucket/foo. get_elapsed_time(seconds)— formats float seconds as00d : 01h : 12m : 03s.- One-liner dual output —
with logger.to_file("run.log"):mirrors every log line to BOTH the Rich-formatted console AND the log file. Perfect for batch jobs. - Timed blocks —
with logger.timed("train epoch"):prints entry + exit + elapsed, notime.perf_counter()boilerplate. logger.exception("msg")— like stdliblogging.exceptionbut the traceback is rendered by Rich.logger.kv(epoch=12, lr=3e-4)— one-line structured key=value logging for training loops.
uv add klogr
# or
pip install klogrRequires Python ≥ 3.10. Runtime deps: rich, pydantic, python-dotenv, beartype, jaxtyping, natsort, sha256sum.
from klogr import get_logger
logger = get_logger()
logger.info("loaded %d samples", 1024) # auto file:line prefix
logger.success("training converged") # green check
logger.warning("validation loss plateaued")
logger.error("OOM on batch 42")
# Pretty-print structured data
logger.print({"epoch": 12, "lr": 3e-4, "loss": 0.21})
# Progress bar that works in parallel
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=8) as pool:
for result in logger.track(pool.map(process, items), total=len(items), description="batch"):
...from klogr import get_logger, LoggingRich, DEFAULT_VERBOSITY
logger = get_logger() # default verbosity
quiet = get_logger({"info": False, "debug": False}) # custom
logger.info(msg) # cyan
logger.success(msg) # green check
logger.warning(msg) # yellow
logger.error(msg) # red
logger.rule(title) # horizontal divider
logger.print(any_object) # rich.print, soft-wrap aware
logger.info_json(json_str) # syntax-colored JSON
logger.track(iter, total, description) # progress barfrom klogr import get_logger
logger = get_logger()
# Scoped to a block — auto-restores console-only on exit:
with logger.to_file("step.log"):
logger.info("written to console AND step.log")
logger.success("step done")
# back to console-only here
# Or set/unset manually if you need broader control:
logger.enable_dual_output("training.log")
logger.info("epoch=12")
logger.disable_dual_output()The file gets the Rich-rendered output verbatim (colors stripped on the
file side, preserved in the terminal). Check logger.is_file_enabled()
to confirm dual output is on.
with logger.timed("train epoch"):
train_one_epoch()
# logs: ▶ train epoch
# ✓ train epoch — 00d : 00h : 12m : 03slogger.kv(epoch=12, lr=3e-4, loss=0.214)
# logs: epoch=12 lr=0.0003 loss=0.214 (with rich coloring)try:
do_work()
except Exception:
logger.exception("do_work failed")
# logs the message at ERROR, then a syntax-highlighted tracebackfrom klogr import lru_cache, DisableableLRUCache, get_cache_dir, sha256sum
@lru_cache(maxsize=128)
def expensive(key: str) -> bytes:
...
# Stable on-disk paths
cache_root = get_cache_dir() # XDG cache root
digest = sha256sum("/path/to/file.bin") # hex stringfrom klogr.path import (
path_join, path_exists, path_mkdir, path_dirname,
path_basename, path_glob, path_is_s3, path_open,
)
path_exists("/tmp/local.bin") # True/False
path_exists("s3://bucket/key.bin") # True/False (same call)
path_mkdir("/tmp/nested/dir", parents=True, exist_ok=True)
for p in path_glob("/data/*.jpg"):
with path_open(p, "rb") as f:
...from klogr import get_elapsed_time
print(get_elapsed_time(86400 + 3661)) # '01d : 01h : 01m : 01s'Runnable scripts live in examples/:
01_basic_logger.py— log levels, formatting, file:line prefix02_pretty_tables.py—LoggingTablefor tabular console output03_progress_tracking.py—logger.track()in parallel workers04_caching.py—@lru_cachewith disable-hook05_path_helpers.py— local + S3 path ops
uv run examples/01_basic_logger.pyklogr/
├── __init__.py # public re-exports
├── logger.py # LoggingRich, LoggingTable, get_logger
├── cache.py # lru_cache, DisableableLRUCache, sha256sum, get_cache_dir
├── path/ # local + S3 path helpers
│ ├── __init__.py
│ ├── ops.py # pure ops (join, dirname, basename, suffix, …)
│ ├── env.py # path_dotenv, path_home, path_expanduser
│ ├── io.py # mkdir, remove, copy, move, read/write
│ └── query.py # exists, is_dir, glob, listdir, stat
└── time.py # get_elapsed_time
uv sync
.venv/bin/pre-commit run --all-files
.venv/bin/pytest
.venv/bin/mypy klogr/MIT — see LICENSE.md.