Skip to main content

Part of otel-data-staleness — see the root README for the project overview and the SDK-vs-Collector comparison.

data-staleness-otel (Python SDK)

Disclaimer: This is an independent, community project. It is not an official OpenTelemetry project and is not endorsed by or affiliated with the OpenTelemetry project or the CNCF. It is built on OpenTelemetry; the data.staleness.* semantic convention is a proposal under discussion with the Semantic Conventions SIG (#3909), at "Development" stability — names may change and it is not an accepted standard.

Vendor-neutral instrumentation for data staleness / data freshness, built on OpenTelemetry, implementing the conventions in ../spec/semantic-conventions.md.

You give the SDK probes that report the event time of the freshest record for a logical source; it emits the standardized data.staleness.* metrics (age, lag, last-update timestamp, records-behind, SLA threshold/breach) via the normal OpenTelemetry metrics pipeline (OTLP, Prometheus, console, etc.).

Install

pip install data-staleness-otel                     # core (imported as `otel_staleness`)
pip install "data-staleness-otel[sql,kafka,cache]"  # with optional integrations

Quick start

from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import (
    ConsoleMetricExporter, PeriodicExportingMetricReader)

from otel_staleness import StalenessMonitor, conventions as sc
from otel_staleness.probes import SQLFreshnessProbe

reader = PeriodicExportingMetricReader(ConsoleMetricExporter())
metrics.set_meter_provider(MeterProvider(metric_readers=[reader]))

monitor = StalenessMonitor()
monitor.add_probe(SQLFreshnessProbe(
    fetch_max_epoch=lambda: get_latest_updated_at(),   # returns Unix seconds or datetime
    source_name="orders", system=sc.System.POSTGRESQL,
    namespace="public", sla_threshold_seconds=300))
monitor.start()

Run the bundled demo (emits all four source types to the console):

python examples/quickstart.py

Probes

Probe Source Method emitted
SQLFreshnessProbe Postgres/MySQL/Snowflake/Redshift/BigQuery max_timestamp
KafkaFreshnessProbe Kafka (per partition) consumer_lag
PipelineFreshnessProbe dbt/Airflow batch jobs run_completion
CacheFreshnessProbe Redis ttl_age
ObjectStoreFreshnessProbe S3/GCS object_mtime
IndexFreshnessProbe Search/vector indexes (Elasticsearch, OpenSearch, Pinecone, Weaviate, Milvus, Qdrant, pgvector) index_lag
ReplicationFreshnessProbe Replicas / CDC targets / multi-region (Postgres replicas, Debezium, DynamoDB global tables) replication_lag
VersionFreshnessProbe RAG / docs version-currency vs a registry (PyPI, npm, GitHub, Docker Hub) version_drift

The first five emit absolute freshness (age vs now). IndexFreshnessProbe / ReplicationFreshnessProbe emit differential (source-relative) freshness — how far a derived/replicated store trails its upstream — via data.staleness.lag plus a data.staleness.relative_to attribute. VersionFreshnessProbe emits version-currency (how many releases behind). Example of the differential form:

from otel_staleness.probes import IndexFreshnessProbe
# how far the vector index trails its source corpus
monitor.add_probe(IndexFreshnessProbe(
    fetch_source_epoch=lambda: newest_source_write_time(),
    fetch_index_epoch=lambda: newest_source_time_present_in_index(),
    index_name="docs-v1", source="public.documents",
    system=sc.System.PINECONE, sla_threshold_seconds=60))

Version-currency for RAG / documentation — is your indexed doc describing the current release? Compares the documented version against a registry:

from otel_staleness.probes import VersionFreshnessProbe
# your RAG corpus documents Kubernetes 1.28; how far behind is it?
monitor.add_probe(VersionFreshnessProbe.from_github_releases(
    "kubernetes/kubernetes", documented_version="1.28.0",
    source_name="k8s-docs", sla_threshold_seconds=60*60*24*14))  # 14 days

Emits records.behind (releases behind), age (how long newer releases have existed unreflected), and version.documented / version.current attributes. Helpers: from_pypi, from_npm, from_github_releases, from_dockerhub (needs the version extra: pip install -e ".[version]"). The comparison logic is pure and unit-tested; unparseable versions raise rather than fabricate a 0.

The documented version rarely needs hard-coding — extract it from the content with otel_staleness.version_extract (frontmatter, a semver token, a JSON field, an HTML <meta>, or a docs URL path), composed with first_of:

from otel_staleness.version_extract import first_of, extract_frontmatter_version, extract_url_path_version
documented = first_of(
    lambda: extract_frontmatter_version(open("docs/networking.md").read()),
    lambda: extract_url_path_version("https://kubernetes.io/docs/v1.28/"),
)  # pass as documented_version=...

See examples/rag_freshness.py for the combined RAG check: index time-lag (IndexFreshnessProbe) and version currency (VersionFreshnessProbe) on the same corpus — the two failure modes that make RAG answers wrong.

Every probe accepts plain callables (returning Unix seconds or a datetime), so it is fully unit-testable without a live backend. SQLFreshnessProbe also ships a from_sqlalchemy(...) convenience constructor.

A failing probe is isolated — it cannot break collection of the others.

End-to-end freshness across hops

Point probes measure freshness at one place. To get true cross-hop age, stamp the origin event-time into OTEL baggage at ingest; any downstream stage reads it back (it rides the standard OTEL context propagators across service/messaging boundaries):

from otel_staleness.freshness_context import stamp_origin, EndToEndFreshness
from opentelemetry import context as otel_context

# at ingest:
otel_context.attach(stamp_origin(event_epoch))
# ...work flows downstream carrying baggage...
# at a later stage:
e2e = EndToEndFreshness()              # emits data.staleness.age, method=end_to_end
e2e.record("orders-pipeline", stage="serve")

Every staleness dimension

StalenessMonitor.start() also emits the extension metrics that capture the dimensions a backend cannot reconstruct: data.staleness.age.peak (Peak AoI), data.staleness.update.interval (cadence histogram), data.staleness.partition.skew (straggler partitions), and data.staleness.probe.errors (a failing probe is now visible, not silently swallowed — pass add_probe(p, name="...") to label it). See ../docs/STALENESS-TAXONOMY.md for the full map of staleness types to mechanisms.

Custom probes

Subclass StalenessProbe and return FreshnessReading objects:

from otel_staleness import StalenessProbe, FreshnessReading, conventions as sc

class MyProbe(StalenessProbe):
    def read(self):
        return [FreshnessReading(
            source_system="custom", source_name="widget",
            last_update_epoch=get_event_time(),       # or age_seconds=...
            sla_threshold_seconds=60)]

Tests

python -m pytest -q                 # 54 tests
PYTHONPATH=src python ../conformance/runner.py   # conformance vectors

Requires Python 3.8+. Optional extras: sql, kafka, cache, otlp, version, dev (e.g. pip install -e ".[dev,version]").

Metadata

Release files for data-staleness-otel 0.4.0

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

Source distribution (sdist)

Source distribution for data-staleness-otel 0.4.0
File Size Uploaded
data_staleness_otel-0.4.0.tar.gz 27.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for data-staleness-otel 0.4.0
File Interpreter ABI Platform
data_staleness_otel-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 53.6 kB

Release files / data_staleness_otel-0.4.0.tar.gz

Download URL data_staleness_otel-0.4.0.tar.gz
Size 27.0 kB
Tags Source
SHA-256 checksum
How to use checksums
a760b0357c67291ac94c33d3f1fd62490e50d0b7f75e23d7092f25b1af3faf7f
BLAKE2b-256 checksum
How to use checksums
0309f75fe1dc12309e07b1ba78596b29baa643afeeb7d529a4824f280ea13cf6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

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 Jul 21, 2026.

Transparency log

Release files / data_staleness_otel-0.4.0-py3-none-any.whl

Download URL data_staleness_otel-0.4.0-py3-none-any.whl
Size 26.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
879f96e99a9f4b18617ca364884294f22fb81924b725ca80460b7a1063d7dc41
BLAKE2b-256 checksum
How to use checksums
651d3de627ddda034fa361b2e7767993512f59dc262abdf26f713bcf6a742ba8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

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 Jul 21, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.0 This release

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