Structured logging for Python applications
Project description
dynalog for Python
dynalog is a structured logging library for Python. It provides colored
console logs, named events, contextual fields, JSON and logfmt output,
filtering, redaction, enrichment, custom highlights, and test-friendly clocks
through one default dynalog instance.
[D7MFXJ01RE3W4 info] Server started source=<unknown>:0 module=<unknown> port=3000 service=api
Console records use compact, sortable LSB32 timestamps. File and memory records retain the original nanosecond timestamp and contain no ANSI color sequences.
Features
- Pythonic: Module-level logging functions and configurable
Dynaloginstances. - Structured: Immutable events with named JSON-compatible payloads.
- Contextual: Shared fields with per-call overrides.
- Readable: Level colors, semantic indicators, and custom highlights.
- Flexible: Plain text, JSON, and compact logfmt output.
- Composable: Console, file, memory, fan-out, and custom destinations.
- Controlled: Minimum-level filtering, recursive redaction, and enrichment.
- Precise: Nanosecond event timestamps and 13-character LSB32 display IDs.
- Testable: Injectable clocks and in-memory capture.
Installation
pip install dynalog
Dynalog requires Python 3.10 or newer.
Quick Start
import dynalog
from dynalog import Level
dynalog.setup(
level=Level.INFO,
context={"service": "api"},
)
dynalog.info("Server started", context={"port": 3000})
dynalog.event("user.login", {"id": 42, "username": "Alice"})
The default destination is Console. Call setup once during application
startup to select a minimum level, output format, shared context, destinations,
or custom clock.
Logging Levels
dynalog.trace("Resolving configuration")
dynalog.debug("Cache connected")
dynalog.info("Server started")
dynalog.warn("Queue depth is elevated", context={"depth": 82})
dynalog.error("Request failed", context={"status": 503})
dynalog.fatal("Worker stopped")
Set a minimum level to discard lower-priority records:
from dynalog import Level
dynalog.setup(level=Level.WARN)
Use allows before computing expensive diagnostic fields:
if dynalog.allows(Level.DEBUG):
dynalog.debug("Cache snapshot", context=collect_cache_details())
Structured Events
Use named events for records intended for search, analytics, metrics, alerts, or automated processing.
dynalog.event(
"payment.completed",
{
"order": "ORD-1042",
"amount": 129.95,
"currency": "USD",
},
)
Messages and structured events use the same timestamp, filtering, context, formatting, and destination configuration.
Events and Sources
Construct an event directly when a caller needs explicit timestamps, context, or source information:
from dynalog import Event, Level, Source
event = Event.message(
"Server started",
timestamp=1_767_225_600_123_456_789,
level=Level.INFO,
context={"port": 3000},
source=Source("app.py", 12, "app"),
)
dynalog.write(event)
Event values are immutable. Transforming an event returns a replacement
instead of changing the original value.
Context
Shared context is merged into every new log record. Per-call context overrides matching shared values without changing later events.
dynalog.setup(
context={"service": "checkout", "region": "west"},
)
dynalog.info(
"Order accepted",
context={"request": "req-42", "region": "central"},
)
Context values can be strings, integers, floats, booleans, or nested mappings.
Output Formats
| Format | Use case |
|---|---|
Output.PLAIN |
Human-readable terminal output |
Output.JSON |
Files, collectors, and structured ingestion |
Output.COMPACT |
Dense single-line logfmt output |
from dynalog import Output
dynalog.setup(output=Output.JSON)
Format an event without writing it to a destination:
from dynalog import Event, Level
event = Event.message(
"Cache connected",
level=Level.INFO,
context={"host": "cache.internal"},
)
plain = dynalog.text(event, "plain")
json = dynalog.text(event, "json")
compact = dynalog.text(event, "compact")
format returns bytes; text returns a decoded string.
Destinations
Send the same record to multiple destinations by listing them in sinks.
Every destination is attempted, and failures are reported together after
dispatch.
import dynalog
from dynalog import Console, File, Memory, Output
memory = Memory()
dynalog.setup(
output=Output.JSON,
sinks=[Console(), File("app.jsonl"), memory],
)
dynalog.info("Ready", context={"port": 3000})
assert len(memory.events) == 1
assert len(memory.records) == 1
Memory.clear() removes captured events and records.
Custom Destinations
A custom destination implements write(record, event):
from dynalog import Event
class Queue:
def __init__(self) -> None:
self.records: list[tuple[bytes, Event]] = []
def write(self, record: bytes, event: Event) -> None:
self.records.append((bytes(record), event))
dynalog.setup(sinks=[Queue()])
Redaction and Enrichment
Redaction returns a replacement event and leaves the original unchanged. Matching keys are masked recursively.
from dynalog import Event, Level
event = Event.message(
"Authentication failed",
timestamp=1_767_225_600_123_456_789,
level=Level.ERROR,
context={
"user": "alice",
"token": "secret",
"credentials": {"password": "secret"},
},
)
safe = dynalog.redact(event, ["password", "token"])
enriched = dynalog.enrich(safe, "api-01")
dynalog.write(enriched)
The default redaction keys are password and token. When no process ID is
provided, enrich uses the current Python process.
Console Highlights
Built-in rules emphasize failures, warnings, HTTP status classes, booleans, identifiers, timing values, paths, hosts, and ports. Add literal or compiled regular expression rules for application-specific indicators.
import re
import dynalog
from dynalog import Console, Tone
console = (
Console(color=True)
.highlight(re.compile(r"\bAUTH-\d+\b"), Tone.MAGENTA)
.highlight("payment declined", Tone.RED)
)
dynalog.setup(sinks=[console])
dynalog.error("Payment AUTH-42 failed", context={"status": 503})
Available tones are RED, YELLOW, GREEN, CYAN, MAGENTA, BLUE,
WHITE, and GRAY. The first custom rule wins when matches overlap.
Color is selected automatically for a compatible terminal and disabled when
NO_COLOR is present. Pass True or False to Console to override automatic
detection.
Testable Time
Provide a clock function that returns nanoseconds as an integer:
import dynalog
from dynalog import Memory
memory = Memory()
now = 1_000_000_000
def clock() -> int:
return now
dynalog.setup(sinks=[memory], clock=clock)
dynalog.info("first")
now += 1_000_000_000
dynalog.info("second")
delta = memory.events[1].timestamp - memory.events[0].timestamp
assert delta == 1_000_000_000
The default clock is time.time_ns.
Public API
| Export | Purpose |
|---|---|
dynalog |
Module-level default logger functions |
Dynalog |
Independent logger instance |
Event |
Immutable message or structured event |
Source |
File, line, and module information |
Level |
Six ordered logging levels |
Output |
Plain, JSON, or compact formatting |
Tone |
Named console highlight color |
Console |
Colored terminal destination |
File |
Append-only file destination |
Memory |
In-memory events and records |
Sink |
Custom destination protocol |
Module-level functions include setup, format, text, allows, redact,
enrich, write, event, trace, debug, info, warn, error, fatal,
and version.
Requirements
- Python 3.10 or newer.
- A wheel compatible with the target Python version and platform, or a Rust toolchain for source builds.
- A supported terminal for automatic ANSI colors.
Publishing
Upload verified distributions to PyPI:
python -m build
python -m twine check dist/*
python -m twine upload dist/*
PyPI authentication can use an API token through TWINE_USERNAME=__token__
and TWINE_PASSWORD.
The distribution includes the Python API, native extension, runtime artifact, documentation, and both license texts.
Limitations and Compatibility
- High-level log methods use unknown source fields unless a source is supplied explicitly.
- File output is append-only and does not currently rotate.
- Destination writes are synchronous; custom destinations should remain fast.
- Nanosecond timestamps do not guarantee uniqueness when events share an instant.
- LSB32 display IDs require every producer and consumer to use the same epoch.
- Building from source requires a supported Rust toolchain and native compiler.
License
Dynalog is dual-licensed under either of the following, at your option:
SPDX: LGPL-3.0-only OR GPL-3.0-only
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dynalog-0.1.0.tar.gz.
File metadata
- Download URL: dynalog-0.1.0.tar.gz
- Upload date:
- Size: 228.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
18b621523155049db6a575fb28d0389189ab2d3c24ff3e34a99e607af1af7242
|
|
| MD5 |
9647c3d8d5822d1090865725bd252d8e
|
|
| BLAKE2b-256 |
9d9f9ce0e6c42c97e154d16f8d759f5d75aa22f88b88d4af9a9b547b266108d9
|
File details
Details for the file dynalog-0.1.0-cp310-abi3-win_amd64.whl.
File metadata
- Download URL: dynalog-0.1.0-cp310-abi3-win_amd64.whl
- Upload date:
- Size: 6.2 MB
- Tags: CPython 3.10+, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3c341da27c2f68ac029402d5b0c640ba97d35ccdda1e70ac056b01adf8c2dcf2
|
|
| MD5 |
ffff5e3ce2e698747aa446f806a65d6e
|
|
| BLAKE2b-256 |
9655b7c17a8b3657b0959cdcc5fec3370a8594bb07aabd3a8ff34372698498eb
|