Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions docs/introduction/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,18 @@ Additional parameters for Litestar integration:

Prometheus's integration for FastStream requires `prometheus_client` package.

To bootstrap Prometheus for FastStream, you must provide additionally:

- `prometheus_middleware_cls`.
To bootstrap Prometheus for FastStream, you must provide at least one of:

- `prometheus_middleware_cls` - the broker metrics middleware, e.g.
`faststream.redis.prometheus.RedisPrometheusMiddleware`. It is constructed with the instrument's
registry and added to the broker, when the application has one.
- `prometheus_collector_registry` - a `prometheus_client.CollectorRegistry` of your own, used in
place of the fresh one the instrument would otherwise build.

With neither, nothing would populate the registry, so the metrics endpoint is not mounted and the
instrument is skipped. Unlike the other frameworks, FastStream serves a private registry rather than
`prometheus_client.REGISTRY`, so an endpoint with no middleware and no injected registry would have
nothing to report.

### Prometheus FastAPI

Expand Down
10 changes: 8 additions & 2 deletions lite_bootstrap/bootstrappers/faststream_bootstrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,10 @@ def _make_collector_registry() -> "prometheus_client.CollectorRegistry":
class FastStreamPrometheusInstrument(PrometheusInstrument):
bootstrap_config: FastStreamConfig
collector_registry: "prometheus_client.CollectorRegistry" = dataclasses.field(init=False)
not_configured_reason = PrometheusInstrument.not_configured_reason + " or prometheus_middleware_cls is missing"
not_configured_reason = (
PrometheusInstrument.not_configured_reason
+ ", or neither prometheus_middleware_cls nor prometheus_collector_registry is set"
)
missing_dependency_message = "prometheus_client is not installed"

def __post_init__(self) -> None:
Expand All @@ -170,7 +173,10 @@ def __post_init__(self) -> None:

@classmethod
def is_configured(cls, bootstrap_config: "FastStreamConfig") -> bool: # ty: ignore[invalid-method-override]
return super().is_configured(bootstrap_config) and bool(bootstrap_config.prometheus_middleware_cls)
return super().is_configured(bootstrap_config) and (
bootstrap_config.prometheus_middleware_cls is not None
or bootstrap_config.prometheus_collector_registry is not None
)

@staticmethod
def dependencies_installed() -> bool:
Expand Down
28 changes: 27 additions & 1 deletion tests/test_faststream_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from lite_bootstrap.bootstrappers.faststream_bootstrapper import (
FastStreamLoggingInstrument,
FastStreamOpenTelemetryInstrument,
FastStreamPrometheusInstrument,
)
from lite_bootstrap.exceptions import ConfigurationError
from tests.conftest import (
Expand Down Expand Up @@ -254,14 +255,18 @@ def test_faststream_build_excluded_urls_covers_prometheus_and_health_paths(broke
assert config_with_health_spans.health_checks_path not in excluded_with # kept when health spans are on


async def test_faststream_prometheus_uses_injected_registry(broker: RedisBroker) -> None:
@pytest.mark.parametrize(
"middleware_cls", [RedisPrometheusMiddleware, None], ids=["with_middleware", "without_middleware"]
)
async def test_faststream_prometheus_uses_injected_registry(broker: RedisBroker, middleware_cls: type | None) -> None:
custom_registry = prometheus_client.CollectorRegistry()
counter_name = f"injected_counter_{uuid.uuid4().hex}_total"
counter = prometheus_client.Counter(counter_name, "Injected registry counter", registry=custom_registry)
counter.inc()

bootstrap_config = dataclasses.replace(
build_faststream_config(broker=broker),
prometheus_middleware_cls=middleware_cls,
prometheus_collector_registry=custom_registry,
)
bootstrapper = FastStreamBootstrapper(bootstrap_config=bootstrap_config)
Expand All @@ -276,6 +281,27 @@ async def test_faststream_prometheus_uses_injected_registry(broker: RedisBroker)
bootstrapper.teardown()


async def test_faststream_prometheus_is_skipped_without_middleware_or_registry(broker: RedisBroker) -> None:
bootstrap_config = dataclasses.replace(
build_faststream_config(broker=broker),
prometheus_middleware_cls=None,
prometheus_collector_registry=None,
)
bootstrapper = FastStreamBootstrapper(bootstrap_config=bootstrap_config)
skipped = dict(bootstrapper.skipped_instruments)
assert FastStreamPrometheusInstrument in skipped
assert "prometheus_collector_registry" in skipped[FastStreamPrometheusInstrument]

application = bootstrapper.bootstrap()
try:
with TestClient(app=application) as test_client:
async with TestRedisBroker(broker):
response = test_client.get(bootstrap_config.prometheus_metrics_path)
assert response.status_code == status.HTTP_404_NOT_FOUND
finally:
bootstrapper.teardown()


def test_faststream_logging_teardown_runs_super_when_broker_write_raises(broker: RedisBroker) -> None:
bootstrap_config = build_faststream_config(broker=broker)
instrument = FastStreamLoggingInstrument(bootstrap_config=bootstrap_config)
Expand Down
Loading