logquill
A structured, leveled logging framework for Python with pluggable transports.
Sibling to logquill on npm
(logquill-js) — same log record shape, same level names, one mental model
across a Python + Node stack.
Status: pre-release, under active development. The core Logger, level
filtering, transports, and the plugin pipeline are implemented;
non-blocking async dispatch is not yet — see CHANGELOG.md for what's
landed so far.
Features
- Structured by default — every call carries a
metadict, not just a message string - Cross-language record shape — identical JSON shape and level names/weights as
logquillon npm - Pluggable transports —
ConsoleTransport(colorized, stderr for errors),FileTransport(rotation),HTTPTransport(batched); write your own by subclassingTransport - Pluggable formatters —
JSONFormatterout of the box; implementformat(record) -> strfor your own - Plugin pipeline —
ContextPlugin,RedactPlugin,SamplingPluginout of the box; a broken plugin can't crash logging - Zero required runtime dependencies — stdlib only;
aiohttpis opt-in, for async HTTP - Typed throughout —
mypy --strictclean on the public API - (planned) non-blocking async dispatch,
contextvars-based context propagation — seeCHANGELOG.md
Install
pip install logquill
Quickstart
from logquill import Level, Logger
logger = Logger("app", level=Level.INFO)
record = logger.info("user signed up", user_id=42, plan="pro")
print(record)
# {'timestamp': '2026-08-27T18:04:12.345Z', 'level': 'INFO', 'logger': 'app',
# 'message': 'user signed up', 'meta': {'user_id': 42, 'plan': 'pro'}}
logger.debug("below threshold, dropped") # -> None, filtered by level
logger.set_level("debug")
logger.debug("now visible") # -> a record dict
Every log call returns the record dict (or None if filtered by level) —
{"timestamp": ISO8601, "level": str, "logger": str, "message": str, "meta": dict},
the same shape shared with logquill on npm.
Use JSONFormatter to serialize a record to the canonical JSON line:
from logquill import JSONFormatter
print(JSONFormatter().format(record))
# '{"timestamp":"2026-08-27T18:04:12.345Z","level":"INFO","logger":"app","message":"user signed up","meta":{"user_id":42,"plan":"pro"}}'
Transports
Attach transports to a Logger to actually write records somewhere. Each
record is dispatched to every attached transport synchronously (non-blocking
dispatch isn't implemented yet):
from logquill import ConsoleTransport, FileTransport, HTTPTransport, Logger
logger = Logger(
"app",
transports=[
ConsoleTransport(), # stdout, ERROR/FATAL to stderr, colorized
FileTransport("app.log", max_bytes=10 * 1024 * 1024, backup_count=5),
HTTPTransport("https://logs.example.com/ingest", batch_size=50),
],
)
logger.info("user signed up", user_id=42, plan="pro")
logger.close() # flushes the file handle and any buffered HTTP batch
Write your own transport by subclassing Transport and implementing
write(formatted, record); format(record) and close() have sensible
defaults. CollectingTransport is a ready-made in-memory transport, handy
in your own tests:
from logquill import CollectingTransport, Logger
sink = CollectingTransport()
logger = Logger("app.test", transports=[sink])
logger.info("hello")
assert sink.records[0]["message"] == "hello"
Plugins
Plugins hook into the pipeline around each log call: before_log(record) can
transform a record or return None to drop it, after_log(record) runs once
it's been dispatched to every transport, and on_error(exc, record) catches
anything a plugin's own hooks raise — a broken plugin can't take down logging.
from logquill import ContextPlugin, Logger, RedactPlugin, SamplingPlugin
logger = Logger("app")
logger.use(ContextPlugin(service="api", env="prod")) # merged into every record's meta
logger.use(RedactPlugin(keys=["password", "token"])) # replaces matching meta values
logger.use(SamplingPlugin(0.1)) # keep ~10% of records that reach this point
logger.info("login attempt", user_id=42, password="hunter2")
# meta: {'service': 'api', 'env': 'prod', 'user_id': 42, 'password': '***'}
# (unless this call was one of the ~90% sampling dropped, in which case it's None)
Write your own by subclassing Plugin; override only the hooks you need.
Development
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev,http,hooks]"
pre-commit install
ruff check .
mypy logquill
pytest
See CONTRIBUTING.md for the PR workflow, the Code of Conduct for community standards, and SECURITY.md for how to report a vulnerability.
Release files for logquill 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| logquill-0.1.3.tar.gz | 18.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| logquill-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.2 kB
Release files / logquill-0.1.3.tar.gz
| Download URL | logquill-0.1.3.tar.gz |
|---|---|
| Size | 18.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
eb77b7b482eb3d124579d2effdc1b7aa7bee5728bf50fc3f5b128bee71b1e1ed
|
|
BLAKE2b-256 checksum How to use checksums |
27ba75b6d7e27a1d17da84722914af6d63af1dcacfdc3d33baabf0fbb2965056
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 29, 2026.
Transparency logRelease files / logquill-0.1.3-py3-none-any.whl
| Download URL | logquill-0.1.3-py3-none-any.whl |
|---|---|
| Size | 13.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
480c452d5620037082c5a6f54f80b513d0706036dc56b605fa3bc194373d3d93
|
|
BLAKE2b-256 checksum How to use checksums |
902a03f203584c02c6c3a86a29680d62934baaa0cec18f39f765d1160da9e0ac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 29, 2026.
Transparency log