Skip to main content

Ergonomic Python logging with Rich-first defaults and a path to structured observability.

Project description

ultilog

CI Docs PyPI Python License

Ergonomic Python logging that starts with a tiny API and scales to structured observability.

from ultilog import get_logger

log = get_logger()
log.info("app.started")

No explicit settings required. The package lazily configures logging on first access, installs a Rich console handler, and returns a standard-library logger.

Install

pip install ultilog

With extras:

pip install "ultilog[structlog]"   # structlog processor bridge
pip install "ultilog[otel]"        # OpenTelemetry traces, logs, metrics
pip install "ultilog[web]"         # FastAPI / Starlette middleware
pip install "ultilog[full]"        # everything

Quickstart

Zero config

from ultilog import get_logger

log = get_logger()
log.info("hello")

One-line helpers

from ultilog import setup_auto, setup_dev, setup_prod, setup_test, get_logger

setup_auto(service_name="my-api")       # env-aware: Rich dev, JSON prod, quiet tests
setup_dev()                           # super-pretty Rich, DEBUG, tracebacks with locals
setup_prod(service_name="my-api")     # JSON, INFO, OTel correlation auto-attached
setup_test()                          # plain WARNING, quiet test output

log = get_logger(__name__)
log.info("ready")

Explicit naming

log = get_logger(__name__)
log = get_logger("my.service")

Custom setup

from ultilog import setup

setup(level="DEBUG", mode="json", force=True)

Presets

Preset Mode Level Rich
dev (default) rich INFO Enabled, tracebacks + locals
test plain WARNING Disabled
prod json INFO Disabled
setup(preset="prod", force=True)

OpenTelemetry auto-correlation

If the opentelemetry package is installed, ultilog auto-attaches a trace correlation filter so trace_id and span_id appear on log records inside any active span — no extra setup required. Install with:

pip install "ultilog[otel]"

Modes

Rich (default)

Pretty console output with colors, tracebacks, and path info.

setup(mode="rich", force=True)
get_logger("demo").info("colored output")

Plain

Simple stream logging for CI, containers, or piped output.

setup(mode="plain", force=True)
get_logger("demo").info("plain output")

JSON

Machine-readable JSON logs for production and log aggregators.

setup(mode="json", force=True)
get_logger("api").info("request.finished")
# {"level": "INFO", "logger": "api", "message": "request.finished", ...}

Context

Context belongs at runtime boundaries, not logger creation time. Use logging_context to bind values that appear in every log record within a scope:

from ultilog import get_logger, logging_context

log = get_logger("worker")

with logging_context(job_id="job_1", queue="emails"):
    log.info("job.started")   # job_id=job_1 queue=emails
    log.info("job.finished")  # job_id=job_1 queue=emails
# context automatically restored

Context is contextvars-based, so it works correctly with asyncio and nested scopes:

with logging_context(outer="1"):
    with logging_context(inner="2"):
        log.info("both")  # outer=1 inner=2
    log.info("outer only")  # outer=1

Lower-level helpers are available for integrations:

from ultilog import bind_context, clear_context, get_context

bind_context(request_id="req_123")
get_context()  # {"request_id": "req_123"}
clear_context()

Framework Integrations

FastAPI / Starlette

from fastapi import FastAPI
from ultilog.integrations import install_fastapi_logging

app = FastAPI()
install_fastapi_logging(app)
# Every request gets logging context with request_id, http.method, http.path

ASGI Middleware

from ultilog.integrations import UltilogASGIMiddleware

app = UltilogASGIMiddleware(app)

Celery

from ultilog.integrations import install_celery_logging

install_celery_logging(app)
# Tasks get context with celery_task_id and celery_task_name

httpx

from ultilog.integrations import install_httpx_logging

client = httpx.Client()
install_httpx_logging(client)
# Logs outgoing HTTP requests at DEBUG level

SQLAlchemy

from ultilog.integrations import install_sqlalchemy_logging

install_sqlalchemy_logging(engine, level=logging.DEBUG)

Structlog

When structlog is installed, ultilog can configure it with pre-built processor chains:

from ultilog.structlog import configure_structlog

configure_structlog()  # console renderer for dev

Choose a renderer that matches your mode:

from ultilog.models.structlog import StructlogSettings

configure_structlog(StructlogSettings(renderer="json"))

OpenTelemetry

With the otel extra installed, configure traces, logs, and metrics:

from ultilog.otel.traces import configure_otel_traces
from ultilog.otel.logs import configure_otel_logs
from ultilog.otel.metrics import configure_otel_metrics

configure_otel_traces(service_name="my-api")
configure_otel_logs(service_name="my-api")
configure_otel_metrics(service_name="my-api")

Or configure all signals at once:

from ultilog.otel.exporters import configure_exporters
from ultilog.models.otel import OTelSettings

configure_exporters(OTelSettings(
    enabled=True,
    service_name="my-api",
    traces_enabled=True,
    logs_enabled=True,
))

Trace/log correlation is automatic when a span is active:

from ultilog.otel.correlation import TraceCorrelationFilter

handler.addFilter(TraceCorrelationFilter())
# Log records get trace_id and span_id attributes

Environment Variables

Settings use the ULTILOG_ prefix with __ for nesting:

export ULTILOG_PRESET=prod
export ULTILOG_LOGGING__LEVEL=DEBUG
export ULTILOG_LOGGING__MODE=json
export ULTILOG_RICH__SHOW_PATH=false

CLI

ultilog doctor --json          # runtime diagnostics
ultilog bootstrap              # inspect a project and print grouped install hints
ultilog bootstrap --json       # machine-readable bootstrap plan
ultilog bootstrap --commands   # install commands plus OTel zero-code commands
ultilog bootstrap --snippet --service-name my-api
ultilog show-config            # dump effective settings
ultilog validate               # check configuration
ultilog demo --mode plain      # emit a demo log line
ultilog demo --mode json

Or via module:

python -m ultilog doctor --json

Project Bootstrap

ultilog bootstrap inspects a project and prints a non-destructive plan for the packages that make logging and observability work end to end. It reads pyproject.toml, lightly scans imports, detects the package manager, and groups recommendations by where they belong:

Target pyproject location Examples
observability-core [project.optional-dependencies] OTel API/SDK/exporter, logging, ASGI/FastAPI/httpx/SQLAlchemy/Redis instrumentation
observability-extra [project.optional-dependencies] Celery, botocore, grpc, urllib3, aiohttp, asyncpg instrumentation
formatting [dependency-groups] ruff
typing [dependency-groups] mypy, pyright, types-* packages
test-core [dependency-groups] pytest, pytest-cov, pytest-mock
coverage [dependency-groups] coverage

For PDM projects, the suggested commands use the right target directly:

pdm add -G observability-core opentelemetry-exporter-otlp ...
pdm add -d -G typing mypy pyright types-requests
pdm add -d -G test-core pytest pytest-cov pytest-mock

When OpenTelemetry zero-code tooling is installed, the plan also shows the official bootstrap commands:

pdm run opentelemetry-bootstrap -a requirements
pdm run opentelemetry-bootstrap -a install
pdm run opentelemetry-instrument python -m your_app

To intentionally run package-manager setup from the CLI, use --apply with one or more groups:

ultilog bootstrap --apply --group observability-core
ultilog bootstrap --apply --group typing --group test-core

For application startup, generate a small setup snippet:

ultilog bootstrap --snippet --service-name my-api

The snippet calls setup_auto(service_name="my-api"), which uses Rich logging in development, quiet plain logging for tests, and production JSON logging with OTel trace/log correlation when APP_ENV=prod or ULTILOG_ENV=prod.

Recommended repo shape:

# src/my_api/logging.py
from ultilog import get_logger, setup_auto

setup_auto(service_name="my-api")
log = get_logger(__name__)

Import that module once from your app entrypoint before creating other loggers.

Testing

ultilog provides test utilities so downstream projects can isolate logging state:

from ultilog.testing.reset import reset_ultilog
from ultilog.testing.capture import capture_logs

reset_ultilog()  # reset package state

with capture_logs("my.logger") as records:
    get_logger("my.logger").info("captured")
assert records[0].getMessage() == "captured"

Advanced Configuration

For full control, use configure() with an explicit settings object:

from ultilog import configure, UltilogSettings

settings = UltilogSettings(
    preset="prod",
    logging=LoggingSettings(level="DEBUG", mode="json"),
    context=ContextSettings(enabled=True),
)
configure(settings, force=True)

Development

pdm sync -G dev -G docs
pdm run pytest                 # tests
pdm run ruff check .           # lint
pdm run mypy src/ultilog       # type-check
pdm run mkdocs serve           # docs preview

License

MIT

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

ultilog-0.4.0.tar.gz (56.4 kB view details)

Uploaded Source

Built Distribution

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

ultilog-0.4.0-py3-none-any.whl (70.9 kB view details)

Uploaded Python 3

File details

Details for the file ultilog-0.4.0.tar.gz.

File metadata

  • Download URL: ultilog-0.4.0.tar.gz
  • Upload date:
  • Size: 56.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for ultilog-0.4.0.tar.gz
Algorithm Hash digest
SHA256 9d4f201d50a72763331377bb50305ad3620da9f4baa71a0fac2d33b4dee977d3
MD5 f24010ed61c28478025dfbce6ee1099f
BLAKE2b-256 8917822da54271834330fed7c46a4dcba703949ae548f132f7c71a51b8eeaf25

See more details on using hashes here.

Provenance

The following attestation bundles were made for ultilog-0.4.0.tar.gz:

Publisher: release.yml on pr1m8/ultilog

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ultilog-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: ultilog-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 70.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for ultilog-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f986dddaff7cf4eb7a3d052ff6acebdd2f5a60d2cf4db29ba091b06695f0e0cd
MD5 2ae9d84524d6f0ddbff204f66aa14866
BLAKE2b-256 1422bf9e878251afd61434f61e0b9480071df528653512052e4663914be995cf

See more details on using hashes here.

Provenance

The following attestation bundles were made for ultilog-0.4.0-py3-none-any.whl:

Publisher: release.yml on pr1m8/ultilog

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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