yaol
Yet another observability library. Wires OpenTelemetry traces, metrics and logs plus structlog and Pyroscope profiling from a single config object.
Usage
from yaol import ObservabilityConfig, setup, shutdown, instrument_asyncpg
config = ObservabilityConfig(
service_name="bot",
service_version="0.1.7",
environment="prod",
otlp_endpoint="http://172.17.0.1:4317",
logger_levels={"sqlalchemy.engine": "WARNING"},
)
setup(config)
instrument_asyncpg()
...
shutdown()
from_env builds the same object from environment variables:
ENV, OTEL_EXPORTER_OTLP_ENDPOINT, LOG_LEVEL, YAOL_JSON_LOGS,
YAOL_EXPORT_TRACES, YAOL_EXPORT_LOGS, YAOL_EXPORT_METRICS, YAOL_METRIC_INTERVAL_MILLIS,
YAOL_PROFILING_ENABLED, PYROSCOPE_ADDRESS, PYROSCOPE_SAMPLE_RATE,
PYROSCOPE_ONCPU, PYROSCOPE_GIL_ONLY, PYROSCOPE_REPORT_PID,
PYROSCOPE_REPORT_THREAD_ID, PYROSCOPE_TAGS,
YAOL_TRACE_SAMPLE_RATIO, YAOL_SPAN_QUEUE_SIZE,
YAOL_SPAN_SCHEDULE_DELAY_MILLIS, YAOL_SPAN_EXPORT_BATCH_SIZE.
PYROSCOPE_TAGS is a key=value,key=value list. Entries without = or with an
empty key are dropped rather than fatal, values may themselves contain =, and
the last duplicate wins.
Kubernetes
Profiles need pod identity to be worth reading — with several replicas pushing
under one application_name, every flamegraph is a blend. Give the agent the
pod name through the downward API:
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: PYROSCOPE_TAGS
value: "pod=$(POD_NAME)"
Resource attributes arrive the same way, but from_env does not read them:
Resource.create() already picks up the standard OTEL_RESOURCE_ATTRIBUTES via
the SDK's own detector, and a second mechanism for one thing only confuses. The
resource_attributes field stays for programmatic configuration.
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: OTEL_RESOURCE_ATTRIBUTES
value: "k8s.pod.name=$(POD_NAME),k8s.namespace.name=$(POD_NS)"
instrument_runtime() collects system.* metrics from /proc, which inside a
container is the node's — system.memory.usage would report the whole host once
per pod. cAdvisor already reports the container's side of that, so ask for the
process's own metrics only:
instrument_runtime(system_metrics=False) # process.* and cpython.*, no system.*
Collecting logs from container stdout instead of OTLP means YAOL_EXPORT_LOGS=false;
the stdout handler stays.
Keeping a trace whole
A trace is only useful if it survives the places where work is handed off. Three of those places need help.
Errors that are handled. A span that catches an exception ends with an OK
status, so the backend shows a green trace for a failed request. SHARED_PROCESSORS
mirrors every log.error/log.exception onto the active span as an exception
event plus an ERROR status, which covers handled errors without touching the
call sites. Use record_exception or fail where there is no log line:
from yaol import fail, record_exception, span
with span("chat.pipeline"):
try:
await run()
except Exception as error:
record_exception(error) # span is now ERROR, with a stack trace
await tell_the_user(error)
span() also records cancellation, which the SDK deliberately ignores — work
killed by a timeout or a shutdown would otherwise export as a healthy span.
Deferred work. asyncio copies the caller's context into every task, so a
task created inside a span adopts it permanently. Long-lived workers must start
clean, and deferred work must resume the context it was scheduled from:
from yaol import capture, spawn
worker = spawn(run_forever()) # clean context, its own traces
later = spawn(handle(batch), context=ctx) # continues the captured trace
capture() takes the snapshot at scheduling time, attached() resumes it, and
detached() drops the ambient trace. When several traces converge on one unit
of work, parent it to the first and pass the rest as links(contexts).
Service boundaries. Instrumented clients propagate the trace themselves —
instrument_httpx() covers SDKs that build their own client, the OpenAI SDK
among them — and instrument_fastapi(app) continues the caller's trace on the
receiving side. inject_headers/extract_context cover hops nothing
instruments.
Signals
- Traces — OTLP gRPC,
BatchSpanProcessor. - Metrics — OTLP gRPC,
PeriodicExportingMetricReader. - Logs — OTLP gRPC alongside the stdout handler; both are attached to the root logger.
- Profiles — Pyroscope push, off by default.
shutdown() flushes every provider, which the previous ad-hoc setup did not do.
Metadata
Release files for yaol 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| yaol-0.3.0.tar.gz | 69.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| yaol-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 82.9 kB
Release files / yaol-0.3.0.tar.gz
| Download URL | yaol-0.3.0.tar.gz |
|---|---|
| Size | 69.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
eff159401d32607a090637f5e1d02bf4528f05bd34722a4dd7a793d1f77bf9d7
|
|
BLAKE2b-256 checksum How to use checksums |
1b7f6a5e31ff18d7279ebf2d8577f5755b283d82b1210bb0206c7d0976b3611c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / yaol-0.3.0-py3-none-any.whl
| Download URL | yaol-0.3.0-py3-none-any.whl |
|---|---|
| Size | 13.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6cc19ee7dcc126476fb6828e455fb7002f948025877daf61a5453db39ce08f45
|
|
BLAKE2b-256 checksum How to use checksums |
cf174e0911910d5af104a02abc72891b614be7cea90934bf50a41fb3a810dd8c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|