1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
// Copyright Materialize, Inc. and contributors. All rights reserved.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0.
use std::time::Duration;
use axum::{body::Body, routing::get, Extension, Router};
use http::{Method, Request, Response, StatusCode};
use prometheus::{Encoder, TextEncoder};
use tower_http::{classify::ServerErrorsFailureClass, trace::TraceLayer};
use tracing::{Level, Span};
use mz_ore::metric;
use mz_ore::metrics::{MetricsRegistry, UIntGauge};
#[derive(Debug)]
pub struct Metrics {
pub needs_update: UIntGauge,
}
impl Metrics {
pub fn register_into(registry: &MetricsRegistry) -> Self {
Self {
needs_update: registry.register(
metric! {
name: "needs_update",
help: "Count of organizations in this cluster which are running outdated pod templates",
}),
}
}
}
pub fn router(registry: MetricsRegistry) -> Router {
add_tracing_layer(
Router::new()
.route("/metrics", get(metrics))
.layer(Extension(registry)),
)
}
#[allow(clippy::unused_async)]
async fn metrics(Extension(registry): Extension<MetricsRegistry>) -> (StatusCode, Vec<u8>) {
let mut buf = vec![];
let encoder = TextEncoder::new();
let metric_families = registry.gather();
encoder.encode(&metric_families, &mut buf).unwrap();
(StatusCode::OK, buf)
}
/// Adds a tracing layer that reports an `INFO` level span per
/// request and reports a `WARN` event when a handler returns a
/// server error to the given Axum Router
///
/// This accepts a router instead of returning a layer itself
/// to avoid dealing with defining generics over a bunch of closures
/// (see <https://users.rust-lang.org/t/how-to-encapsulate-a-builder-that-depends-on-a-closure/71139/6>)
///
/// And this also can't be returned as a Router::new()::layer(TraceLayer)...
/// because the TraceLayer needs to be added to a Router after
/// all routes are defined, as it won't trace any routes defined
/// on the router after it's attached.
fn add_tracing_layer<S>(router: Router<S>) -> Router<S>
where
S: Clone + Send + Sync + 'static,
{
router.layer(TraceLayer::new_for_http()
.make_span_with(|request: &Request<Body>| {
// This ugly macro is needed, unfortunately (and
// copied from tower-http), because
// `tracing::span!` required the level argument to
// be static. Meaning we can't just pass
// `self.level`.
// Don't log Authorization headers
let mut headers = request.headers().clone();
_ = headers.remove(http::header::AUTHORIZATION);
macro_rules! make_span {
($level:expr) => {
tracing::span!(
$level,
"HTTP request",
"request.uri" = %request.uri(),
"request.version" = ?request.version(),
"request.method" = %request.method(),
"request.headers" = ?headers,
"response.status" = tracing::field::Empty,
"response.status_code" = tracing::field::Empty,
"response.headers" = tracing::field::Empty,
)
}
}
if request.uri().path() == "/api/health" || request.method() == Method::OPTIONS {
return make_span!(Level::DEBUG);
}
make_span!(Level::INFO)
})
.on_response(|response: &Response<Body>, _latency, span: &Span| {
span.record(
"response.status",
&tracing::field::display(response.status()),
);
span.record(
"response.status_code",
&tracing::field::display(response.status().as_u16()),
);
span.record(
"response.headers",
&tracing::field::debug(response.headers()),
);
// Emit an event at the same level as the span. For the same reason as noted in the comment
// above we can't use `tracing::event!(dynamic_level, ...)` since the level argument
// needs to be static
if span.metadata().and_then(|m| Some(m.level())).unwrap_or(&Level::DEBUG) == &Level::DEBUG {
tracing::debug!(msg = "HTTP response generated", response = ?response, status_code = response.status().as_u16());
} else {
tracing::info!(msg = "HTTP response generated", response = ?response, status_code = response.status().as_u16());
}
})
.on_failure(
|error: ServerErrorsFailureClass, _latency: Duration, _span: &Span| {
tracing::warn!(msg = "HTTP request handling error", error = ?error);
},
))
}