Skip to main content

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   # grouped setup 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 --no-sync -G observability-core opentelemetry-exporter-otlp ...
pdm add --no-sync -d -G typing mypy pyright types-requests
pdm add --no-sync -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-instrument python -m your_app
# Optional after reviewing requirements:
pdm run opentelemetry-bootstrap -a install

ultilog bootstrap runs a read-only environment check for human output and --apply. If pip check reports a conflict, the CLI prints the conflicting requirement and a repair command before touching packages.

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
ultilog bootstrap --apply --all

For PDM, --apply uses pdm add --no-sync so it updates pyproject.toml and the lockfile without pruning the active virtualenv. Run pdm sync with the groups you actually want only after reviewing the result.

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

Metadata

Release files for ultilog 0.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ultilog 0.4.1
File Size Uploaded
ultilog-0.4.1.tar.gz 59.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ultilog 0.4.1
File Interpreter ABI Platform
ultilog-0.4.1-py3-none-any.whl Python 3 none any Details

Total release size: 131.6 kB

Release files / ultilog-0.4.1.tar.gz

Download URL ultilog-0.4.1.tar.gz
Size 59.0 kB
Tags Source
SHA-256 checksum
How to use checksums
2e76cc83216f88eb73a188bb941eddef2aa09266dbbf448e7004b02743e20e37
BLAKE2b-256 checksum
How to use checksums
67a65ff35ed6e6b94fd153476650fdd80f3cb7d00fe0a84913d261175a8516ea
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 22, 2026.

Transparency log

Release files / ultilog-0.4.1-py3-none-any.whl

Download URL ultilog-0.4.1-py3-none-any.whl
Size 72.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bf2a8f4ca4b84403ef92388ee6700057a7b943ad37bd38681178422c83ba76a6
BLAKE2b-256 checksum
How to use checksums
87cf77d053c49dea4625be03f89105ac42fb915df208aa72922338dd7709ba0c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 22, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.1 This release

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page