tailstate
Rotation-aware incremental reading of rotated log files with persisted state.
The importable package is tailstate, with code under src/tailstate/. The
distribution name in pyproject.toml is tailstate.
It persists inode + byte offset as JSON so a later run can continue from the
last fully processed line, or follow rotation to unread files with the same
basename prefix.
- Python: 3.11+
- Runtime dependencies: standard library only
- State format: JSON (UTF-8 text), human-readable and stable across Python versions
- License: MIT (see LICENSE)
Docs
README.md: overview, install, quickstart- docs/development.md: dev commands, tests, layout, portability notes
AGENTS.md: non-obvious traps for coding agentssrc/tailstate/: module and class docstrings define the precise behavior
Main Pieces
RotatedLogFileSavedState: discovers matching log files in one directory, ordered by mtime, and yields open text streams fromlogs()LogSavedState: typed persistedinode/seekstateTimedLogProcessor: processes yielded files under a totalSIGALRMbudgetPersistentObjandJsonPersistentObj: generic JSON-backed persistence helpersLog4jLogLineProcessor: timed processor for log4j-style%d{ISO8601} %p %m%nlinesensure_dir,tmp_file,find_file_by_inode: filesystem helpers
Important Behavior
- Matching uses a plain filename prefix in the same directory.
app.log.1andapp.log-extraboth matchapp.log. logs()yields streams from inside an open-file context. The file stays open until the caller resumes the generator.- If byte offsets matter, use
readline()or explicittell()/seek()instead offor line in f. - Log streams are opened as UTF-8 with
errors="replace". - Timed processing uses
SIGALRMon Unix. Timeouts can still affect rotation state and partial progress in subtle ways.
Install
Using uv from the project root:
uv sync
uv run pytest
uv run mypy src tests
uv run black src tests
Without uv:
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
pip install black mypy pytest pytest-cov
python -m pytest
Demo
Primary walkthrough:
uv run python examples/rotation_walkthrough.py
This zero-argument demo shows:
- initial reading from offset 0
- append-only continuation using saved
inode+seek - incomplete trailing-line deferral with explicit
readline()/seek() - rotation-aware continuation from
app.log.1into a freshapp.log
Optional higher-level example:
uv run python examples/log4j_metrics.py
Quickstart
Incremental rotation-aware reading:
from tailstate import RotatedLogFileSavedState
with RotatedLogFileSavedState("/var/log/app/app.log", "/var/run/app-state.json") as state:
for log_file in state.logs():
while True:
line = log_file.readline()
if not line:
break
...
Timed multi-file processing:
from tailstate import RotatedLogFileSavedState, TimedLogProcessor
class MyProc(TimedLogProcessor):
def process_log(self, log_file):
... # return (value, skip_others)
def combine_values(self, old, new):
...
with RotatedLogFileSavedState(log_path, state_path) as state:
result = MyProc(max_duration=60).process(state)
log4j-style line parsing:
from tailstate import Log4jLogLineProcessor
class MyScrape(Log4jLogLineProcessor):
def get_metrics(self):
return {"level": {"error": 0}}
def process_level_error(self, message):
return {"level": {"error": 1}}
License
MIT. See LICENSE.
Metadata
Release files for tailstate 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tailstate-0.2.0.tar.gz | 15.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tailstate-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 28.3 kB
Release files / tailstate-0.2.0.tar.gz
| Download URL | tailstate-0.2.0.tar.gz |
|---|---|
| Size | 15.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b981be7ece45d698ee336d231c9617e672b53bf85bc73e87941c32bf006a0bd2
|
|
BLAKE2b-256 checksum How to use checksums |
f7aa06be0e115334d275d11a60c8345e71b35d32694305925fa14277000a1898
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 21, 2026.
Transparency logRelease files / tailstate-0.2.0-py3-none-any.whl
| Download URL | tailstate-0.2.0-py3-none-any.whl |
|---|---|
| Size | 12.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0709cb97bcc68f79e5894040a3fc2abc87923b190aae7dbd6c3db8d0c6c0f292
|
|
BLAKE2b-256 checksum How to use checksums |
a0f82057be7bfb19c54deedce12d8f232d579c85ccdec99f344861da4dd15e75
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 21, 2026.
Transparency log