Skip to main content

Structured logging for Python applications

Project description

dynalog for Python

PyPI Python License

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 Dynalog instances.
  • 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dynalog-0.1.0.tar.gz (228.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dynalog-0.1.0-cp310-abi3-win_amd64.whl (6.2 MB view details)

Uploaded CPython 3.10+Windows x86-64

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

Hashes for dynalog-0.1.0.tar.gz
Algorithm Hash digest
SHA256 18b621523155049db6a575fb28d0389189ab2d3c24ff3e34a99e607af1af7242
MD5 9647c3d8d5822d1090865725bd252d8e
BLAKE2b-256 9d9f9ce0e6c42c97e154d16f8d759f5d75aa22f88b88d4af9a9b547b266108d9

See more details on using hashes here.

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

Hashes for dynalog-0.1.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 3c341da27c2f68ac029402d5b0c640ba97d35ccdda1e70ac056b01adf8c2dcf2
MD5 ffff5e3ce2e698747aa446f806a65d6e
BLAKE2b-256 9655b7c17a8b3657b0959cdcc5fec3370a8594bb07aabd3a8ff34372698498eb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page