Skip to main content

Tee_Logger

A Python logger that writes to a dated log file and optionally mirrors output to stdout with ANSI colors. Supports in-place compression, automatic log archival, and smart caller line attribution.

Features

  • Tee logging — file-only (debug, info, warning, error, critical, exception, …) or stdout+file (teeprint, teewarning, teeerror, teeexception, …)
  • Dated layout — {programName}_log/YYYY-MM-DD/{programName}_YYYY-MM-DD_HH-MM-SS.log, rolling over to a new day at midnight
  • Caller attribution — log lines include abbreviated file:line of the direct caller
  • Compression — write .zst/.xz/.gz/.bz2 logs directly (crash-safe, see below), or tar.xz archive old day-folders
  • Maintenance — auto-compress and delete logs by age, in a background thread

Installation

pip install tee-logger

No runtime dependencies; src/Tee_Logger.py also works when copied on its own.

Requires Python 3.6+. In-place zstd compression requires Python 3.14+ with zstd support in the build; otherwise it falls back to xz.

Quick start

from Tee_Logger import teeLogger

tl = teeLogger(systemLogFileDir='.', programName='MyApp')
tl.info('file only')
tl.teeprint('stdout and file')
tl.teeerror('error to both')

Compressed logs:

tl = teeLogger(
    programName='MyApp',
    in_place_compression=True,  # first available of zstd, xz, gzip, bz2; or name one
    compression_level=3,        # optional, backend-specific
)

Compressed logs are written in batches so they compress well, and flushed to disk:

  • immediately for WARNING and above (warning, error, critical, exception, …),
  • otherwise at most flush_interval seconds (default 30) after a record is written, even if the program goes quiet.

So if the process is killed (OOM, SIGKILL, segfault), at most the last flush_interval seconds of DEBUG/INFO records are lost; warnings and errors are already on disk. gzip and zstd flush mid-stream; xz and bz2 cannot, so each flush finishes the current stream and starts a new one in the same file (xz -dc, bzip2 -dc, lzma.open, bz2.open read them all). A file that is still being written may end in an unfinished stream: zcat/zstd -dc print everything and then warn about it. flush_interval=0 flushes every record, which costs a lot of compression (gzip/zstd files about twice as large).

Disable file logging:

tl = teeLogger(noLog=True)

Configuration

Parameter Default Description
systemLogFileDir '.' Root directory for log folders; on failure falls back to a private teeLogger-<uid>/ (mode 0700) under the system temp dir
programName caller file name without extension Log file prefix and stdlib logger name (see below)
suppressPrintout not stdout.isatty() Hide console output from tee* / printTable and the Log file: banner; file-only methods never print
disable_colors auto Plain [LEVEL] msg output instead of ANSI colors; by default only when the stream is not a terminal
callerStackDepth -1 Auto-resolve direct caller; use 0+ for manual stack offset
in_place_compression None zstd/zst, xz/lzma, gzip, bz2, or True (first available, in that order)
compression_level backend default gzip/bz2 compresslevel, xz preset, zstd level (xz presets 7–9 need hundreds of MB of RAM)
flush_interval 30 Max seconds before compressed records reach disk; 0 = every record
binary_mode True Binary append mode for log files
compressLogAfterMonths 2 Archive day-folders older than N months (0 = off)
deleteLogAfterYears 2 Delete day-folders older than N years (0 = off)
noLog False Disable file logging
fileDescriptorLength 15 Width of file:line in log records
encoding 'utf-8' Log file encoding
collapse_single_day_logs auto One file per day when compressing in-place

Per-call override: tl.info('msg', callerStackDepth=3).

Shared logger name

programName is passed to logging.getLogger(programName). A second teeLogger with the same name reuses that logger and replaces the previous file handler so only one active log file is attached. That is intentional (one live log per name). Use distinct programName values if two components need independent log files at the same time.

Log layout

MyApp_log/
├── MyApp_latest.log          # symlink to active log (Unix); .zst/.xz/.gz/.bz2 suffix when compressed
├── 2024-11-30.tar.xz         # archived day-folder
└── 2025-02-10/
    └── MyApp_2025-02-10_01-33-26.log

A process that runs past midnight closes its file and continues in the new day's folder; the first line there names the file it continued from.

Log files are opened without following symlinks, so a link planted at a predictable log path cannot redirect writes into another file. Still, avoid pointing systemLogFileDir (or running with the default .) at a directory other users can write to.

The logger's own messages (the Log file: … banner, fallbacks, cleanup failures) go to stderr, so they never mix into a tool's piped stdout. The banner is hidden when suppressPrintout is on.

Log line format:

2025-02-10 01:33:26,623 [INFO    ] [MyApp:42      ] message text

Caller stack depth

With the default callerStackDepth=-1, teeLogger skips its own frames and records the direct caller — your code or your wrapper, not internal Tee_Logger methods.

def my_wrapper(msg):
    tl.info(msg)          # log shows this line

def business():
    my_wrapper('hello')   # log shows my_wrapper, not business

Use a non-negative callerStackDepth or a per-call override for advanced tuning.

Log maintenance

On initialization and at each midnight rollover, {programName}_log/ is scanned for YYYY-MM-DD folders (age comes from the folder name):

  1. Delete folders and archives older than deleteLogAfterYears
  2. Compress folders older than compressLogAfterMonths to .tar.xz

Folders with a file modified in the last 24 hours are left alone, in case another process is still writing there. The work runs in a background thread; the program exits only after an archive in progress is finished. cleanup_old_logs() runs the same maintenance synchronously.

Compression uses system tar+xz when available, otherwise Python's tarfile. It is safe against crashes and concurrent cleanups: the folder is first renamed to YYYY-MM-DD.compressing-<pid>/, the archive is written to a temporary file and renamed into place, and only then is the folder deleted. If archiving fails, the folder is put back; a claim left by a crashed process is picked up again after 24 hours.

API reference

Full docstrings and examples live in the module:

python -c "import Tee_Logger; help(Tee_Logger.teeLogger)"

Logging methods (all accept callerStackDepth=):

File only File + console
debug teedebug
info, ok teeprint, teeok
warning teewarning
error teeerror
critical teecritical
exception (adds traceback) teeexception (traceback in file, one-line summary on console)
log(msg, level) teelog(msg, level), printTable(data)

Public helpers: abbreviate_filename, pretty_format_table, printWithColor, getCallerInfo, compress_folder, teeLogger.

Testing

Primary entry point (pytest, including doctests in src/Tee_Logger.py):

uv sync --extra dev
uv run pytest

Secondary entry points:

python src/Tee_Logger.py --test
python src/Tee_Logger.py -V

CI runs on Python 3.9, 3.11, 3.12, and 3.14, plus 3.6 in a python:3.6 container.

Upgrading to 6.42 from 6.40 or earlier

  • The default programName drops the file extension: myscript.py now logs to myscript_log/ instead of myscript.py_log/. Pass programName='myscript.py' to keep the old folder, or move it.
  • in_place_compression=True picks zstd on Python 3.14+ (previously always xz), so the file suffix may change.
  • compression_level now takes effect; it was silently ignored before.
  • The Log file: banner and other internal messages moved from stdout to stderr.
  • python-dateutil is no longer a dependency.
  • When the log directory is not writable, logs fall back to $TMPDIR/teeLogger-<uid>/ instead of /tmp directly.

License

GPL-3.0-or-later

Metadata

Release files for tee-logger 6.42

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tee-logger 6.42
File Size Uploaded Provenance
tee_logger-6.42.tar.gz 33.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for tee-logger 6.42
File Interpreter ABI Platform
tee_logger-6.42-py3-none-any.whl Python 3 none any Details

Total release size: 53.2 kB

Release files / tee_logger-6.42.tar.gz

Download URL tee_logger-6.42.tar.gz
Size 33.3 kB
Tags Source
SHA-256 checksum
How to use checksums
be2cc0592203de2628a908cc1a71c5e0bda095265efb527229644b397f82946a
BLAKE2b-256 checksum
How to use checksums
92b46444fc4e1c774e987956ce9b0f0873421a4978a76270f880dc306d07d5b5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / tee_logger-6.42-py3-none-any.whl

Download URL tee_logger-6.42-py3-none-any.whl
Size 19.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7b8b59bf1726d5c80089a8f11c4ea2dc36b4647240530533600ecdf8fd8a4432
BLAKE2b-256 checksum
How to use checksums
b3d898805ecaa236648fca9f5c2d086581308f4a78a96276506bc16e73749411
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for tee-logger 6.42

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page