cells-log
cells-log is a lightweight structured logging kernel for Python services, CLIs, async workers and data pipelines.
It keeps Python's stdlib logging ecosystem underneath, but exposes a small bound-logger API with bootstrap-safe initialization, structured fields, ContextVar-based trace context, file routing and optional queue-based writing.
The import package path is cells.log.
Why this package exists
Most projects start with logging.basicConfig() and eventually grow a mixed set of console logs, JSON logs, module files, request IDs and ad-hoc wrappers. cells-log is designed to keep that layer small and stable:
- Application code uses one bound logger API.
- Infrastructure code controls sinks, formatters and lifecycle.
- Project-specific policy, such as field serialization or redaction, is injected through hooks instead of hard-coded into the kernel.
- Bootstrap logs work before full application settings are available.
Installation
From source:
python -m pip install -e .
From a built wheel:
python -m pip install dist/cells_log-*.whl
Quick start
from pathlib import Path
from cells.log import (
LogConfig,
configure_logging,
get_logger,
reset_trace_id,
set_trace_id,
shutdown_logging,
)
configure_logging(
LogConfig(
app_name="app",
level="INFO",
format="json",
log_dir=Path("./logs"),
enable_console=True,
enable_module_file=True,
enable_total_file=True,
total_file="app.log",
module_name_prefixes=("app",),
)
)
trace_token = set_trace_id("trace-001")
try:
log = get_logger("app.worker").bind(stage="worker")
log.info("task completed", task_id="t1", duration_ms=12.3)
finally:
reset_trace_id(trace_token)
shutdown_logging()
Example JSON line:
{"timestamp":"2026-05-11T00:00:00+00:00","level":"INFO","logger":"app.worker","message":"task completed","trace_id":"trace-001","path":"/app/worker.py:42","func":"run","fields":{"channel":"app.worker","log_tag":"app_worker","stage":"worker","task_id":"t1","duration_ms":12.3}}
Public API
from cells.log import (
LogConfig,
bootstrap_logging,
configure_logging,
configure_logging_from_env,
get_logger,
get_queue_stats,
set_trace_id,
reset_trace_id,
bind_log_context,
reset_log_context,
shutdown_logging,
)
bootstrap_logging()
Initializes console-only logging from environment variables. This is safe before application settings are loaded. File sinks and queue writing stay disabled during bootstrap, even when their environment flags are enabled.
from cells.log import bootstrap_logging, get_logger
bootstrap_logging(app_name="demo")
get_logger("demo.boot").info("bootstrap started")
configure_logging()
Configures the production handler graph from an explicit LogConfig.
from cells.log import LogConfig, configure_logging
configure_logging(LogConfig(app_name="demo", level="DEBUG", format="text"))
Reconfiguration prepares the replacement while the current configuration continues serving logs. Preparation failure leaves the current configuration active. Handover briefly gates delivery to drain accepted queue records and transfer file streams; activation failure restores the previous configuration. After a successful switch, old resources are cleaned up while the replacement serves logs.
Use force=False to keep an existing production configuration. Call shutdown_logging() before process exit to drain queued records and close managed handlers.
configure_logging_from_env()
Builds LogConfig from environment variables.
from pathlib import Path
from cells.log import configure_logging_from_env
configure_logging_from_env(
app_name="demo",
default_log_dir=Path("./logs"),
env_prefix="CELLS_LOG",
)
Environment variables
When using configure_logging_from_env(env_prefix="CELLS_LOG"), supported variables include:
| Variable | Meaning |
|---|---|
CELLS_LOG_LEVEL |
TRACE, DEBUG, INFO, WARNING, ERROR, CRITICAL |
CELLS_LOG_FORMAT |
json or text |
CELLS_LOG_DIR |
Log directory used by file sinks |
CELLS_LOG_FILE |
Total log file path or file name |
CELLS_LOG_ENABLE_CONSOLE |
Enable console sink |
CELLS_LOG_ENABLE_MODULE_FILE |
Enable per-module file sink |
CELLS_LOG_ENABLE_TOTAL_FILE |
Enable total file sink |
CELLS_LOG_ROTATION |
daily or size |
CELLS_LOG_RETENTION |
Number of rotated files to keep |
CELLS_LOG_SIZE_LIMIT |
Size rotation limit, for example 100MB |
CELLS_LOG_USE_QUEUE |
Enable in-process QueueHandler / QueueListener |
CELLS_LOG_QUEUE_CAPACITY |
Maximum number of buffered log records |
CELLS_LOG_QUEUE_FULL_POLICY |
drop, block, or sync_fallback |
CELLS_LOG_CAPTURE_WARNINGS |
Route Python warnings into logging |
CELLS_LOG_MANAGE_ROOT_LOGGER |
Route root logger records through cells-log |
Configuration model
LogConfig controls console and file sinks, rotation, queue backpressure, root/warnings capture, the fallback trace ID and serialization hooks. See cells/log/config.py for all fields and defaults.
See docs/architecture.md for the full design boundary.
Queue backpressure
Production logging uses a bounded queue by default, with a capacity of 10,000 records. Choose queue_full_policy according to the application's delivery and latency requirements:
| Policy | Behavior when the queue is full |
|---|---|
drop (default) |
Discard the record and increment dropped_records |
block |
Wait up to queue_block_timeout seconds, then discard and count the record |
sync_fallback |
Write the record synchronously on the calling thread |
get_queue_stats() returns enabled, capacity, depth and dropped_records for the active queue. Set use_queue=False for direct synchronous writing.
Context propagation
trace_id and log context are stored in ContextVar, so they work naturally in async pipelines and request-scoped code.
from cells.log import bind_log_context, reset_log_context, set_trace_id, reset_trace_id
trace_token = set_trace_id("request-001")
ctx_token = bind_log_context(request_id="request-001", user_id="u1")
try:
...
finally:
reset_log_context(ctx_token)
reset_trace_id(trace_token)
Hook model
cells-log intentionally does not hard-code business policy. Field serialization, redaction, pruning and normalization should be attached through hooks.
from cells.log import LogConfig, configure_logging
def field_processor(fields: dict) -> dict:
fields = dict(fields)
if "authorization" in fields:
fields["authorization"] = "***"
return fields
configure_logging(LogConfig(field_processor=field_processor))
Custom JSON serializer:
from cells import json
from cells.log import LogConfig, configure_logging
configure_logging(LogConfig(json_dumps=json.dumps))
File layout
With module and total logs enabled:
logs/
app.log
modules/
app.log
app_worker.log
_external.log
_overflow.log
Routing notes:
module_name_prefixes=("app",)keeps application logs in their own module files.- Loggers outside the allowed prefix are routed into
_external.log. - If too many dynamic logger names are created, excess records are routed into
_overflow.loginstead of opening unlimited file descriptors. - Names that map to the same file share a rotating handler, including case variants on Windows; each record retains its original logger name.
GitHub Actions
This repository includes two workflows:
.github/workflows/build.yml: Python 3.11–3.14 tests on Linux and Windows, wheel artifact build, and GitHub Release publishing..github/workflows/publish-pypi.yml: release/manual wheel build, metadata check and PyPI publish.
After tests and wheel metadata checks pass, build.yml publishes the built wheel according to the triggering ref:
| Trigger | GitHub Release |
|---|---|
Push to main |
Update the rolling tag to the tested commit and publish a prerelease titled rolling |
Push a v* tag |
Publish a release using the tag as its title |
| Other pushes, pull requests, manual builds | Build and upload workflow artifacts only |
The rolling release replaces matching wheel assets and removes wheel assets from older versions. It is not marked as the latest stable release. Publishing uses the built-in GITHUB_TOKEN with contents: write permission limited to the release job.
Releases created with GITHUB_TOKEN do not trigger the separate PyPI workflow; use its manual trigger for those builds. See GitHub's workflow triggering documentation.
Expected repository secret:
PYPI_API_TOKEN
Optional R2/S3 artifact variables used by build.yml on v* tags:
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
S3_BUCKET
S3_ENDPOINT
S3_PREFIX
S3_REGION
Development
python -m pip install -e . pytest
python -m pytest -v
Build wheel locally:
python -m pip install build twine
python -m build --wheel
python -m twine check dist/*.whl
Design status
cells-log is intentionally small. The current package aims to cover the common 90% production logging cases without becoming a full observability platform. For OpenTelemetry, sampling, rate limiting, or redaction, prefer integration modules and hooks over changing the core logger API.
Metadata
Release files for cells-log 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| cells_log-0.2.2-py3-none-any.whl | Python 3 | none | any | Details for cells_log-0.2.2-py3-none-any.whl |
Release files / cells_log-0.2.2-py3-none-any.whl
| Download URL | cells_log-0.2.2-py3-none-any.whl |
|---|---|
| Size | 29.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d5303169d79198f701b726384595a7360a04d4215e13cd42a2ec2f6dae0ebbac
|
|
BLAKE2b-256 checksum How to use checksums |
153a4661a97ef59042d7380d946a799c408a47ed798657877d28b999277d29b3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Security for cells-log 0.2.2
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.