Skip to content

Commit 182c357

Browse files
authored
FastStream Prometheus: use the default registry and make the metrics path opt-in (#375)
1 parent 7193241 commit 182c357

4 files changed

Lines changed: 45 additions & 33 deletions

File tree

‎docs/integrations/faststream.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ bootstrapper_config = FastStreamConfig(
7979
KafkaBroker: KafkaTelemetryMiddleware,
8080
RedisBroker: RedisTelemetryMiddleware,
8181
},
82+
prometheus_metrics_path="/metrics",
8283
prometheus_middleware_cls={
8384
KafkaBroker: KafkaPrometheusMiddleware,
8485
RedisBroker: RedisPrometheusMiddleware,

‎docs/introduction/configuration.md‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ every removed option.
129129
Prometheus is the cheapest of the three non-logging instruments; see [Performance](performance.md).
130130

131131
Prometheus is on by default when its extra is installed, serving metrics at `prometheus_metrics_path`
132-
(default: `/metrics`). Set `prometheus_metrics_path` to an empty string to disable it.
132+
(default: `/metrics`). Set `prometheus_metrics_path` to an empty string to disable it. FastStream is
133+
the exception: its default is empty; see [Prometheus FastStream](#prometheus-faststream).
133134

134135
Additional parameters:
135136

@@ -164,20 +165,18 @@ Additional parameters for Litestar integration:
164165

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

167-
To bootstrap Prometheus for FastStream, you must provide at least one of:
168+
On FastStream `prometheus_metrics_path` defaults to an empty string since 2.0, so the metrics endpoint
169+
is off until you set one. Once set, it serves `prometheus_client.REGISTRY`, the registry FastAPI,
170+
Litestar and FastMCP serve too. That includes the default process and platform collectors and any
171+
collector you register without naming a registry.
168172

169173
- `prometheus_middleware_cls` - the broker metrics middleware, e.g.
170174
`faststream.redis.prometheus.RedisPrometheusMiddleware`. It is constructed with the instrument's
171175
registry and added to every broker of the first broker's type. For an application with brokers of
172176
different types, pass a mapping from broker class to middleware class instead; see
173177
[multiple brokers](../integrations/faststream.md#multiple-brokers).
174-
- `prometheus_collector_registry` - a `prometheus_client.CollectorRegistry` of your own, used in
175-
place of the fresh one the instrument would otherwise build.
176-
177-
With neither, nothing would populate the registry, so the metrics endpoint is not mounted and the
178-
instrument is skipped. Unlike the other frameworks, FastStream serves a private registry rather than
179-
`prometheus_client.REGISTRY`, so an endpoint with no middleware and no injected registry would have
180-
nothing to report.
178+
- `prometheus_collector_registry` - a `prometheus_client.CollectorRegistry` of your own, served and
179+
passed to the middleware in place of `prometheus_client.REGISTRY`.
181180

182181
### Prometheus FastAPI
183182

‎lite_bootstrap/bootstrappers/faststream_bootstrapper.py‎

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ class FastStreamConfig(
112112
| Mapping[type, type[FastStreamPrometheusMiddlewareProtocol]]
113113
| None
114114
) = None
115+
prometheus_metrics_path: str = ""
115116
prometheus_collector_registry: "prometheus_client.CollectorRegistry | None" = None
116117
faststream_log_level: int = logging.WARNING
117118
faststream_health_check_broker_timeout: float = 5.0
@@ -196,29 +197,14 @@ def bootstrap(self) -> None:
196197
)
197198

198199

199-
def _make_collector_registry() -> "prometheus_client.CollectorRegistry":
200-
return prometheus_client.CollectorRegistry()
201-
202-
203200
@dataclasses.dataclass(kw_only=True)
204201
class FastStreamPrometheusInstrument(PrometheusInstrument[FastStreamConfig]):
205202
collector_registry: "prometheus_client.CollectorRegistry" = dataclasses.field(init=False)
206-
not_configured_reason = (
207-
PrometheusInstrument.not_configured_reason
208-
+ ", or neither prometheus_middleware_cls nor prometheus_collector_registry is set"
209-
)
210203
missing_dependency_message = "prometheus_client is not installed"
211204

212205
def __post_init__(self) -> None:
213206
injected = self.bootstrap_config.prometheus_collector_registry
214-
self.collector_registry = injected if injected is not None else _make_collector_registry()
215-
216-
@classmethod
217-
def is_configured(cls, bootstrap_config: "FastStreamConfig") -> bool: # ty: ignore[invalid-method-override]
218-
return super().is_configured(bootstrap_config) and (
219-
bootstrap_config.prometheus_middleware_cls is not None
220-
or bootstrap_config.prometheus_collector_registry is not None
221-
)
207+
self.collector_registry = injected if injected is not None else prometheus_client.REGISTRY
222208

223209
@staticmethod
224210
def dependencies_installed() -> bool:

‎tests/test_faststream_bootstrap.py‎

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -392,25 +392,51 @@ async def test_faststream_prometheus_uses_injected_registry(broker: RedisBroker,
392392
bootstrapper.teardown()
393393

394394

395-
async def test_faststream_prometheus_is_skipped_without_middleware_or_registry(broker: RedisBroker) -> None:
396-
bootstrap_config = dataclasses.replace(
397-
build_faststream_config(broker=broker),
398-
prometheus_middleware_cls=None,
399-
prometheus_collector_registry=None,
395+
def test_faststream_prometheus_is_skipped_without_metrics_path(broker: RedisBroker) -> None:
396+
bootstrap_config = FastStreamConfig(
397+
application=faststream.asgi.AsgiFastStream(broker),
398+
prometheus_middleware_cls=RedisPrometheusMiddleware,
399+
logging_buffer_capacity=0,
400400
)
401401
bootstrapper = FastStreamBootstrapper(bootstrap_config=bootstrap_config)
402402
skipped = dict(bootstrapper.skipped_instruments)
403-
assert FastStreamPrometheusInstrument in skipped
404-
assert "prometheus_collector_registry" in skipped[FastStreamPrometheusInstrument]
403+
assert skipped[FastStreamPrometheusInstrument] == "prometheus_metrics_path is empty or not valid"
404+
405405

406+
async def test_faststream_prometheus_serves_the_default_registry(broker: RedisBroker) -> None:
407+
counter_name = f"default_registry_counter_{uuid.uuid4().hex}_total"
408+
counter = prometheus_client.Counter(counter_name, "Default registry counter")
409+
counter.inc()
410+
bootstrap_config = dataclasses.replace(
411+
build_faststream_config(broker=broker), prometheus_middleware_cls=None, prometheus_collector_registry=None
412+
)
413+
bootstrapper = FastStreamBootstrapper(bootstrap_config=bootstrap_config)
406414
application = bootstrapper.bootstrap()
407415
try:
416+
instruments = [one for one in bootstrapper.instruments if isinstance(one, FastStreamPrometheusInstrument)]
417+
assert [one.collector_registry for one in instruments] == [prometheus_client.REGISTRY]
408418
with TestClient(app=application) as test_client:
409419
async with TestRedisBroker(broker):
410420
response = test_client.get(bootstrap_config.prometheus_metrics_path)
411-
assert response.status_code == status.HTTP_404_NOT_FOUND
421+
assert response.status_code == status.HTTP_200_OK
422+
assert counter_name.encode() in response.content
423+
assert b"python_info" in response.content
412424
finally:
413425
bootstrapper.teardown()
426+
prometheus_client.REGISTRY.unregister(counter)
427+
428+
429+
def test_faststream_default_registry_takes_two_brokers_and_a_second_bootstrap() -> None:
430+
for _ in range(2):
431+
brokers: list[BrokerUsecase[typing.Any, typing.Any, typing.Any]] = [RedisBroker(), RedisBroker()]
432+
bootstrapper = _bootstrap_with_brokers(
433+
brokers, opentelemetry_log_traces=False, opentelemetry_middleware_cls=None
434+
)
435+
try:
436+
for one in brokers:
437+
assert _middleware_types(one) == [RedisPrometheusMiddleware]
438+
finally:
439+
bootstrapper.teardown()
414440

415441

416442
def test_faststream_logging_teardown_runs_super_when_broker_write_raises(

0 commit comments

Comments
 (0)