Эта страница переведена с английского языковой моделью. При любом расхождении верен английский оригинал.
Middleware и наблюдаемость
Приложение оборачивает каждого подписчика в два слоя: один пишет в журнал доставки и их исходы, другой, собственный, предупреждает о медленных обработчиках.
Layer оборачивает обработчик в другой обработчик.
Слой выполняет код до тела обработчика, после него или вместо него и передаёт исход дальше.
Слой приложения оборачивает всех подписчиков, которых монтирует приложение. Слой роутера оборачивает только подписчиков этого роутера.
RustStream сам записывает события через tracing: запуск сервиса, подписку на каждую тему, остановку сервиса.
TracingLayer добавляет событие для каждой доставки: delivery received на уровне DEBUG, затем её исход. Подтверждение (ack) записывается на уровне INFO, переобработка и отбрасывание - на уровне WARN.
Фича logging выводит эти события, как показала глава 1. Вместо неё подходит любой другой подписчик tracing.
Раздел Наблюдаемость описывает метрики Prometheus и экспорт в OpenTelemetry. Они подключаются как слои тем же способом.
Собственный слой магазина
SlowHandlers замеряет время каждого обработчика и предупреждает, если обработчик работает дольше порога.
Предупреждение называет подписку, имя которой слой читает из контекста доставки.
У слоя нет формы на макросах: в обеих вкладках это один и тот же обычный тип.
BlanketLayer позволяет приложению применять его к обработчикам, типы которых приложение скрывает.
- Макросы
- Вручную
/// Warns about every delivery whose handler ran longer than the threshold.
#[derive(Clone)]
pub(crate) struct SlowHandlers {
threshold: Duration,
}
impl SlowHandlers {
pub(crate) const fn over(threshold: Duration) -> Self {
Self { threshold }
}
}
/// The handler `SlowHandlers` makes of the one it wraps.
pub(crate) struct Timed<H> {
inner: H,
threshold: Duration,
}
impl<H> Layer<H> for SlowHandlers {
type Handler = Timed<H>;
fn layer(&self, inner: H) -> Timed<H> {
Timed {
inner,
threshold: self.threshold,
}
}
}
impl<M, C, S, H> Handler<M, C, S> for Timed<H>
where
M: Send + Sync,
C: Send,
S: Send + Sync,
H: Handler<M, C, S>,
{
async fn handle(&self, msg: &M, ctx: &mut Context<'_, C, S>) -> HandlerOutcome {
let started = Instant::now();
let outcome = self.inner.handle(msg, ctx).await;
let took = started.elapsed();
if took > self.threshold {
tracing::warn!(subscription = ctx.name(), ?took, "slow handler");
}
outcome
}
}
// The app wraps handlers whose types it hides, through this generic method.
impl BlanketLayer for SlowHandlers {
fn apply<M, C, S, H>(&self, handler: H) -> impl Handler<M, C, S> + 'static
where
M: Send + Sync + 'static,
C: Send + 'static,
S: Send + Sync + 'static,
H: Handler<M, C, S> + 'static,
{
self.layer(handler)
}
}
/// Warns about every delivery whose handler ran longer than the threshold.
#[derive(Clone)]
pub(crate) struct SlowHandlers {
threshold: Duration,
}
impl SlowHandlers {
pub(crate) const fn over(threshold: Duration) -> Self {
Self { threshold }
}
}
/// The handler `SlowHandlers` makes of the one it wraps.
pub(crate) struct Timed<H> {
inner: H,
threshold: Duration,
}
impl<H> Layer<H> for SlowHandlers {
type Handler = Timed<H>;
fn layer(&self, inner: H) -> Timed<H> {
Timed {
inner,
threshold: self.threshold,
}
}
}
impl<M, C, S, H> Handler<M, C, S> for Timed<H>
where
M: Send + Sync,
C: Send,
S: Send + Sync,
H: Handler<M, C, S>,
{
async fn handle(&self, msg: &M, ctx: &mut Context<'_, C, S>) -> HandlerOutcome {
let started = Instant::now();
let outcome = self.inner.handle(msg, ctx).await;
let took = started.elapsed();
if took > self.threshold {
tracing::warn!(subscription = ctx.name(), ?took, "slow handler");
}
outcome
}
}
// The app wraps handlers whose types it hides, through this generic method.
impl BlanketLayer for SlowHandlers {
fn apply<M, C, S, H>(&self, handler: H) -> impl Handler<M, C, S> + 'static
where
M: Send + Sync + 'static,
C: Send + 'static,
S: Send + Sync + 'static,
H: Handler<M, C, S> + 'static,
{
self.layer(handler)
}
}
Приложение
service.rs собирает магазин.
layer добавляет слой приложению. Первый добавленный слой - внешний.
Роутер каждого модуля монтируется через include_router, и слои приложения оборачивают его подписчиков.
on_startup создаёт разделяемое состояние из главы 7, а info() - описание из главы 9.
Функция принимает брокер параметром, поэтому точка входа командной строки и HTTP-сервер из главы 10 собирают один и тот же магазин.
- Макросы
- Вручную
/// The whole shop on `broker`. The command-line entry point and the HTTP host both build it here.
pub(crate) fn shop(
broker: impl BrokerRegistration<Broker = MemoryBroker>,
) -> impl App<State = Shop> {
RustStream::new(info())
.on_startup(async move |()| Shop::open().await)
.layer(TracingLayer::default())
.layer(SlowHandlers::over(Duration::from_millis(250)))
.with_broker(broker, |b| {
b.include_router(orders::routes());
b.include_router(payments::routes());
b.include_router(shipping::routes());
b.include_router(notifications::routes());
})
}
/// The whole shop on `broker`. The command-line entry point and the HTTP host both build it here.
pub(crate) fn shop(
broker: impl BrokerRegistration<Broker = MemoryBroker>,
) -> impl App<State = Shop> {
RustStream::new(info())
.on_startup(async move |()| Shop::open().await)
.layer(TracingLayer::default())
.layer(SlowHandlers::over(Duration::from_millis(250)))
.with_broker(broker, |b| {
b.include_router(orders::routes());
b.include_router(payments::routes());
b.include_router(shipping::routes());
b.include_router(notifications::routes());
})
}
Что показывает журнал
Заказ, оформленный запросом с идентификатором req-1, оставляет по строке на каждый шаг:
INFO guide_http::orders order placed request=req-1 order=1 total_cents=14400
INFO ruststream::dispatch handler ack
INFO ruststream::dispatch handler ack
INFO ruststream::dispatch handler ack
INFO guide_http::notifications mail sent server=smtp://mail.internal:25 to="ada@example.com" text="We have your order 1."
INFO ruststream::dispatch handler ack
INFO ruststream::dispatch handler ack
INFO ruststream::dispatch handler ack
INFO guide_http::notifications mail sent server=smtp://mail.internal:25 to="ada@example.com" text="Order 1 is on its way: TRK-1-2."
INFO ruststream::dispatch handler ack
RUST_LOG настраивает, что выводится, например RUST_LOG=ruststream=debug,info.