juniper-observability
Shared observability primitives for the Juniper platform — health models, structured logging, middleware, and crash-safe Prometheus collector registration.
juniper-observability is the single place every Juniper service gets its cross-cutting observability
wiring, so each service doesn't re-implement it: Pydantic health-response models for the
/v1/health/ready endpoint, structured-JSON logging with request_id propagation, the Starlette
RequestIdMiddleware and PrometheusMiddleware, idempotent prometheus_client collector helpers,
the matching pytest reset fixture, and Sentry initialisation.
The collector helpers are the headline. Registering a Counter / Gauge / Histogram / … twice —
across a module reload or a pytest-fixture reset — normally raises
Duplicated timeseries in CollectorRegistry. register_or_reuse adopts the existing collector
instead, so registration is safe to repeat.
Part of the Juniper platform. juniper-observability is the shared observability layer of Juniper — a multi-package ML research platform built around constructive (Cascade-Correlation) and recurrent neural networks. It's a small infrastructure library; you don't need the rest of the platform to use it.
Install
pip install juniper-observability # core
pip install "juniper-observability[prometheus]" # + Prometheus middleware/utilities
pip install "juniper-observability[sentry]" # + Sentry init
pip install "juniper-observability[all]" # everything
It is also pulled in by the meta-package's juniper-ml[tools] and juniper-ml[all] extras.
Quick start
from prometheus_client import CollectorRegistry, Counter
from juniper_observability import register_or_reuse
registry = CollectorRegistry()
c1 = register_or_reuse(Counter, "demo_total", "demo counter", registry=registry)
c2 = register_or_reuse(Counter, "demo_total", "demo counter", registry=registry)
assert c1 is c2 # the second call adopts the existing collector — no exception
For tests that touch collector state, use the canonical reset fixture:
from juniper_observability.testing import reset_prometheus_registry # pytest fixture
What's in the box
| Surface | What it gives you |
|---|---|
register_or_reuse / register_fresh / register_info_or_update / lazy_register_or_reuse |
Idempotent prometheus_client collector registration (adopt-existing is the default). |
testing.reset_prometheus_registry |
Pytest fixture that resets collector state between tests. |
RequestIdMiddleware, PrometheusMiddleware |
Starlette middlewares applied across the service fleet. |
| Health-response models | Pydantic models for the /v1/health/ready endpoint. |
| Structured-JSON logging | Logging configuration with request_id propagation. |
| Sentry init | configure_sentry passes include_local_variables=False (no switch to turn locals back on) and the SEC-10 before_send hook always runs. The hook also deletes frame vars. |
configure_sentry never captures frame locals. Sentry's EventScrubber redacts locals by name, and the local that held the configured API key during the comparison in the juniper-canopy#683 finding is not on that list. The before_send hook deletes vars as well, because the opt-in PureEvalIntegration (sentry-sdk 2.58.0) writes them without consulting include_local_variables. Releases up to and including 0.4.0 use the SDK default (True). There is no parameter to turn locals back on.
Operator notes: juniper-ml REFERENCE.
Per-service metrics stay in each repo. This package exposes only cross-cutting infrastructure;
service-specific metric definitions (training-loop counters, dataset-gen histograms, websocket gauges)
live in their owning repo and register via register_or_reuse against prometheus_client.REGISTRY.
Consumers
The service repos — juniper-data,
juniper-cascor,
juniper-canopy — import the middlewares, health models,
and collector helpers (pin >=0.2.0). The client libraries
(juniper-data-client,
juniper-cascor-client) pull it in via their
[observability] extra for X-Request-ID propagation and Prometheus counters.
Status
Live on PyPI, published independently of the meta-package on juniper-observability-v* tags. The
current version is shown by the badge above; see CHANGELOG.md. Consumers pin
juniper-observability>=0.2.0.
Compatibility policy — pinning, not deprecation cycles
This package ships no deprecation machinery: no DeprecationWarning, no
PendingDeprecationWarning, no legacy aliases. That is deliberate, not an oversight
(defect-register APD-ECO-007).
While pre-1.0, compatibility is managed by external pinning — consumers pin a floor
and a ceiling, so each 0.x is a compatibility boundary and a breaking change is
absorbed by a ceiling raise the consumer performs deliberately. That holds while the
consumer set is a known, enumerable handful of first-party repositories.
What would change this: a third-party dependant, or 1.0 — at which point the
consumer set stops being enumerable and a census stops being a sufficient substitute
for a deprecation cycle. When that happens, reuse the implementations this ecosystem
already has rather than writing a third:
juniper_config_tools.env_with_legacy_alias for env-var renames, and the
juniper-data-client alias pattern (a DeprecationWarning with a correct stacklevel
and a dated removal window) for API renames. Re-derivation is exactly how the
env-var lookup drifted across three services before it was consolidated.
Design
Part of the Juniper ML research platform. Rationale and history:
register_or_reusehelper design + migration history- METRICS-MON R2.1 shared-observability design (archived)
License
MIT — see LICENSE.
Metadata
Release files for juniper-observability 0.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| juniper_observability-0.4.1.tar.gz | 47.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| juniper_observability-0.4.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 77.5 kB
Release files / juniper_observability-0.4.1.tar.gz
| Download URL | juniper_observability-0.4.1.tar.gz |
|---|---|
| Size | 47.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e60245ce9c9ba78009e4eacc2157de73c4a9eb02d9dfb0ec90fdc2990b564e37
|
|
BLAKE2b-256 checksum How to use checksums |
08f518d8b664153180a0cd564ec032ca0b5af391601a8e896463c34236fb0fda
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.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 Oct 10, 2026.
Transparency logRelease files / juniper_observability-0.4.1-py3-none-any.whl
| Download URL | juniper_observability-0.4.1-py3-none-any.whl |
|---|---|
| Size | 29.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
85ca34d908b0dbcb30ddece4d9384ec767ea8f72c7559e89c06bb3a28223ee40
|
|
BLAKE2b-256 checksum How to use checksums |
438911611a646b80947940c5c774aef7e15a86ad41b7db5418a702c01bb1169e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.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 Oct 10, 2026.
Transparency log