Ergonomic Python logging with Rich-first defaults and a path to structured observability.
Project description
ultilog
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")
Explicit naming
log = get_logger(__name__)
log = get_logger("my.service")
Optional setup
from ultilog import setup, get_logger
setup(level="DEBUG", force=True)
log = get_logger()
Presets
Three presets configure sensible defaults:
| Preset | Mode | Level | Rich |
|---|---|---|---|
dev (default) |
rich |
INFO | Enabled |
test |
plain |
WARNING | Disabled |
prod |
json |
INFO | Disabled |
setup(preset="prod", force=True)
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 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
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
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 ultilog-0.2.0.tar.gz.
File metadata
- Download URL: ultilog-0.2.0.tar.gz
- Upload date:
- Size: 44.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bccb921d6a0f12844c0e4bd84c1a7a7cafd4eee53f73d0e9d248fe15e6c980f2
|
|
| MD5 |
c5acafa2ee3f82343c706876bb069f93
|
|
| BLAKE2b-256 |
3d430a98931df1887b8143dc16d961fe332748b1abf209679cc1fd051de059f0
|
Provenance
The following attestation bundles were made for ultilog-0.2.0.tar.gz:
Publisher:
release.yml on pr1m8/ultilog
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ultilog-0.2.0.tar.gz -
Subject digest:
bccb921d6a0f12844c0e4bd84c1a7a7cafd4eee53f73d0e9d248fe15e6c980f2 - Sigstore transparency entry: 1415899897
- Sigstore integration time:
-
Permalink:
pr1m8/ultilog@19977094ff45f1927ceec97ba82a60a9a9986d7e -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/pr1m8
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@19977094ff45f1927ceec97ba82a60a9a9986d7e -
Trigger Event:
push
-
Statement type:
File details
Details for the file ultilog-0.2.0-py3-none-any.whl.
File metadata
- Download URL: ultilog-0.2.0-py3-none-any.whl
- Upload date:
- Size: 61.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
34a6cfec3895fdab3b06b55f4b15b6d34e9e14f30f5e3b9ec70e6512815a071c
|
|
| MD5 |
8eb6938d8e2a79709dc0a1776beac9d9
|
|
| BLAKE2b-256 |
1c71156792e748f86b774b59e39024d7f54e6081b8b7a2e6a46acbfe19fe3a52
|
Provenance
The following attestation bundles were made for ultilog-0.2.0-py3-none-any.whl:
Publisher:
release.yml on pr1m8/ultilog
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ultilog-0.2.0-py3-none-any.whl -
Subject digest:
34a6cfec3895fdab3b06b55f4b15b6d34e9e14f30f5e3b9ec70e6512815a071c - Sigstore transparency entry: 1415899990
- Sigstore integration time:
-
Permalink:
pr1m8/ultilog@19977094ff45f1927ceec97ba82a60a9a9986d7e -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/pr1m8
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@19977094ff45f1927ceec97ba82a60a9a9986d7e -
Trigger Event:
push
-
Statement type: