dd-logging
Shared logging helpers for Digital Duck projects.
One consistent pattern for CLI tools and apps: a timestamped log file per run, in
plain text, JSONL, or both. Stdlib only (logging, json).
Used by SPL.py, graflang, spl-llm and spl-flow.
Install
pip install dd-logging # from PyPI
pip install -e /path/to/dd-logging # local editable install
Usage
from dd_logging import setup_logging, setup_console_logging, get_logger, log_event, disable_logging
# 1. Once per process (CLI entry point or app startup)
log_path = setup_logging(
"run",
root_name="my_app", # top-level logger namespace
adapter="openrouter", # appended to the filename (optional)
model="meta/llama-3", # appended after the adapter, made filename-safe (optional)
log_level="info", # debug | info | warning | error
log_format="both", # text | jsonl | both
console=False, # also log to stderr (e.g. --verbose)
)
# → logs/run-openrouter-20260215-143022.log (returned)
# logs/run-openrouter-20260215-143022.jsonl (when log_format="both")
# 2. In each module
_log = get_logger("nodes.text2spl", root_name="my_app")
_log.info("translating query len=%d", len(query))
# 3. Structured events: readable in the .log, queryable fields in the .jsonl
log_event(_log, "node.fire", node="draft", fired=2)
# .log 12:00:01 INFO my_app.nodes.text2spl node.fire node=draft fired=2
# .jsonl {"ts": "...", "level": "INFO", "logger": "my_app.nodes.text2spl",
# "msg": "node.fire node=draft fired=2", "event": "node.fire",
# "node": "draft", "fired": 2}
# 3b. CLIs that want warnings on screen but a log file only for some commands
setup_console_logging("my_app", "info") # stderr only; a later setup_logging() replaces it
# 4. Silence everything (e.g. --no-log)
disable_logging("my_app")
setup_logging returns the primary file: the text file for text/both, the JSONL
file for jsonl.
JSONL format
One object per line: ts (UTC ISO-8601, ms), level, logger, msg, then any
extra={...} fields, plus exc (formatted traceback) for log.exception(...).
Non-serialisable values are rendered with str(). Field names that clash with
LogRecord attributes in log_event get an f_ prefix.
jq -c 'select(.event=="node.fire") | {ts,node}' logs/run-*.jsonl
Environment overrides
Used only when the matching argument is left unset:
| Variable | Meaning |
|---|---|
DD_LOG_LEVEL |
debug / info / warning / error |
DD_LOG_DIR |
directory for log files (default ./logs) |
DD_LOG_FORMAT |
text / jsonl / both |
Per-project facade (recommended)
# myapp/logging_config.py
from pathlib import Path
from dd_logging import ProjectLogging
_pl = ProjectLogging("my_app", log_dir=Path(__file__).resolve().parent.parent / "logs")
get_logger, setup_logging, setup_console_logging, disable_logging, log_event = (
_pl.get_logger, _pl.setup_logging, _pl.setup_console_logging, _pl.disable_logging, _pl.log_event)
Call-sites then write get_logger("api") and setup_logging("run", log_format="both").
Log file naming
<log_dir>/<run_name>[-<adapter>][-<model>]-<YYYYMMDD-HHMMSS>.<log|jsonl>
Logger hierarchy
my_app ← root (file handlers attached here)
├── my_app.api ← get_logger("api", "my_app")
└── my_app.nodes.text2spl ← get_logger("nodes.text2spl", "my_app")
Child loggers inherit the root's handlers; no per-module setup.
Design notes
propagate=Falseon the root: no duplicate output when the Python root logger already has a handler (Streamlit, pytest, Jupyter).- Repeat setup is safe: handlers from an earlier
setup_loggingcall are closed and replaced (file and console). - Silent until configured:
get_loggergives the root aNullHandler, so a library that logs beforesetup_loggingdoesn't trip Python's last-resort stderr. - No third-party dependencies.
Projects using dd-logging
| Project | Root name |
|---|---|
| SPL.py | spl |
| graflang | graflang |
| spl-flow | spl_flow |
Development
pip install -e ".[dev]" && pytest
License
MIT
Metadata
Release files for dd-logging 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 | Provenance | |
|---|---|---|---|---|
| dd_logging-0.2.0.tar.gz | 9.6 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dd_logging-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 18.4 kB
Release files / dd_logging-0.2.0.tar.gz
| Download URL | dd_logging-0.2.0.tar.gz |
|---|---|
| Size | 9.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8706eb46e5b97a695608d7237fd1a9453552e860ceeed861339a6dd1fc85d882
|
|
BLAKE2b-256 checksum How to use checksums |
79ef47a9f214da453216d203aa8e9c7ee3d917fcc27be2fd15e6a1888df32755
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.5
|
Release files / dd_logging-0.2.0-py3-none-any.whl
| Download URL | dd_logging-0.2.0-py3-none-any.whl |
|---|---|
| Size | 8.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d91544e56e64aedabdee8b30f6f01fb494727b511d706fdaf27e9f6fe8e09685
|
|
BLAKE2b-256 checksum How to use checksums |
6a49339f86a0c04b07ff6ac7bae7b18e1c06899626cd924b92bcfada3ff2e29e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.5
|
Security for dd-logging 0.2.0
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.