33import json
44import logging
55import typing
6+ from collections .abc import Mapping , Sequence
67
78from lite_bootstrap import import_checker
89from lite_bootstrap .bootstrappers .base import BaseBootstrapper
10+ from lite_bootstrap .helpers .warn import warn_at_caller
911from lite_bootstrap .instruments .healthchecks_instrument import HealthChecksConfig , HealthChecksInstrument
1012from lite_bootstrap .instruments .logging_instrument import LoggingConfig , LoggingInstrument
1113from lite_bootstrap .instruments .opentelemetry_instrument import OpenTelemetryConfig , OpenTelemetryInstrument
@@ -65,6 +67,34 @@ def __init__(
6567 def __call__ (self , msg : typing .Any , / , * , context : typing .Any ) -> typing .Any : ... # noqa: ANN401
6668
6769
70+ _MiddlewareT = typing .TypeVar ("_MiddlewareT" )
71+
72+
73+ def _middleware_targets (
74+ brokers : Sequence [typing .Any ],
75+ configured : "type[_MiddlewareT] | Mapping[type, type[_MiddlewareT]]" ,
76+ field_name : str ,
77+ ) -> list [tuple [typing .Any , "type[_MiddlewareT]" ]]:
78+ by_broker_type = configured if isinstance (configured , Mapping ) else {type (brokers [0 ]): configured }
79+ targets : list [tuple [typing .Any , type [_MiddlewareT ]]] = []
80+ skipped : dict [str , None ] = {}
81+ for broker in brokers :
82+ middleware_cls = next (
83+ (by_broker_type [one ] for one in type (broker ).__mro__ if one in by_broker_type ),
84+ None ,
85+ )
86+ if middleware_cls is None :
87+ skipped [repr (type (broker ).__name__ )] = None
88+ else :
89+ targets .append ((broker , middleware_cls ))
90+ if skipped :
91+ warn_at_caller (
92+ f"FastStream brokers { ', ' .join (skipped )} are not instrumented: { field_name } has no middleware "
93+ "class for their type. Pass a mapping from broker class to middleware class to cover them."
94+ )
95+ return targets
96+
97+
6898def _make_asgi_faststream () -> "AsgiFastStream" :
6999 return AsgiFastStream ()
70100
@@ -74,8 +104,14 @@ class FastStreamConfig(
74104 HealthChecksConfig , LoggingConfig , OpenTelemetryConfig , PrometheusConfig , PyroscopeConfig , SentryConfig
75105):
76106 application : "AsgiFastStream" = dataclasses .field (default_factory = _make_asgi_faststream )
77- opentelemetry_middleware_cls : type [FastStreamTelemetryMiddlewareProtocol ] | None = None
78- prometheus_middleware_cls : type [FastStreamPrometheusMiddlewareProtocol ] | None = None
107+ opentelemetry_middleware_cls : (
108+ type [FastStreamTelemetryMiddlewareProtocol ] | Mapping [type , type [FastStreamTelemetryMiddlewareProtocol ]] | None
109+ ) = None
110+ prometheus_middleware_cls : (
111+ type [FastStreamPrometheusMiddlewareProtocol ]
112+ | Mapping [type , type [FastStreamPrometheusMiddlewareProtocol ]]
113+ | None
114+ ) = None
79115 prometheus_collector_registry : "prometheus_client.CollectorRegistry | None" = None
80116 faststream_log_level : int = logging .WARNING
81117 faststream_health_check_broker_timeout : float = 5.0
@@ -112,27 +148,25 @@ async def _define_framework_health_status(self) -> bool:
112148
113149@dataclasses .dataclass (kw_only = True )
114150class FastStreamLoggingInstrument (LoggingInstrument [FastStreamConfig ]):
115- _prior_broker_params_storage : typing .Any = dataclasses .field (default = None , init = False , repr = False , compare = False )
116- _broker_logger_replaced : bool = dataclasses .field (default = False , init = False , repr = False , compare = False )
151+ _prior_broker_params_storages : list [tuple [typing .Any , typing .Any ]] = dataclasses .field (
152+ default_factory = list , init = False , repr = False , compare = False
153+ )
117154
118155 def bootstrap (self ) -> None :
119156 super ().bootstrap ()
120- broker = self .bootstrap_config .application .broker
121- if broker is not None and import_checker .is_structlog_installed and import_checker .is_faststream_installed :
157+ brokers = self .bootstrap_config .application .brokers
158+ if brokers and import_checker .is_structlog_installed and import_checker .is_faststream_installed :
122159 logger = structlog .get_logger ("faststream" )
123160 logger .setLevel (self .bootstrap_config .faststream_log_level )
124- self . _prior_broker_params_storage = broker . config . logger . params_storage
125- broker .config .logger .params_storage = ManualLoggerStorage ( logger )
126- self . _broker_logger_replaced = True
161+ for broker in brokers :
162+ self . _prior_broker_params_storages . append (( broker , broker .config .logger .params_storage ) )
163+ broker . config . logger . params_storage = ManualLoggerStorage ( logger )
127164
128165 def teardown (self ) -> None :
129166 try :
130- if self ._broker_logger_replaced :
131- broker = self .bootstrap_config .application .broker
132- if broker is not None :
133- broker .config .logger .params_storage = self ._prior_broker_params_storage
134- self ._broker_logger_replaced = False
135- self ._prior_broker_params_storage = None
167+ while self ._prior_broker_params_storages :
168+ broker , prior_storage = self ._prior_broker_params_storages .pop ()
169+ broker .config .logger .params_storage = prior_storage
136170 finally :
137171 super ().teardown ()
138172
@@ -142,12 +176,13 @@ class FastStreamOpenTelemetryInstrument(OpenTelemetryInstrument[FastStreamConfig
142176 def bootstrap (self ) -> None :
143177 super ().bootstrap ()
144178 config = self .bootstrap_config
145- if config .opentelemetry_middleware_cls and config .application .broker :
146- config .application .broker .add_middleware (
147- config .opentelemetry_middleware_cls (
148- tracer_provider = get_tracer_provider (), meter_provider = get_meter_provider ()
179+ if config .opentelemetry_middleware_cls and config .application .brokers :
180+ for broker , middleware_cls in _middleware_targets (
181+ config .application .brokers , config .opentelemetry_middleware_cls , "opentelemetry_middleware_cls"
182+ ):
183+ broker .add_middleware (
184+ middleware_cls (tracer_provider = get_tracer_provider (), meter_provider = get_meter_provider ())
149185 )
150- )
151186
152187
153188def _make_collector_registry () -> "prometheus_client.CollectorRegistry" :
@@ -183,8 +218,11 @@ def bootstrap(self) -> None:
183218 config .application .mount (
184219 config .prometheus_metrics_path , prometheus_client .make_asgi_app (self .collector_registry )
185220 )
186- if config .prometheus_middleware_cls and config .application .broker :
187- config .application .broker .add_middleware (config .prometheus_middleware_cls (registry = self .collector_registry ))
221+ if config .prometheus_middleware_cls and config .application .brokers :
222+ for broker , middleware_cls in _middleware_targets (
223+ config .application .brokers , config .prometheus_middleware_cls , "prometheus_middleware_cls"
224+ ):
225+ broker .add_middleware (middleware_cls (registry = self .collector_registry ))
188226
189227
190228class FastStreamBootstrapper (BaseBootstrapper ["AsgiFastStream" ]):
0 commit comments