Add http request metrics

This commit is contained in:
photino 2023-01-13 22:00:01 +08:00
parent 21b323d5a1
commit 5b676373a0
6 changed files with 35 additions and 11 deletions

View File

@ -16,7 +16,6 @@ port = 6082
[database] [database]
type = "postgres" type = "postgres"
schema = "public"
namespace = "dc" namespace = "dc"
[[postgres]] [[postgres]]
@ -28,6 +27,8 @@ password = "QAx01wnh1i5ER713zfHmZi6dIUYn/Iq9ag+iUGtvKzEFJFYW"
[tracing] [tracing]
filter = "info,sqlx=trace,tower_http=trace,zino=trace,zino_core=trace" filter = "info,sqlx=trace,tower_http=trace,zino=trace,zino_core=trace"
display-filename = true
display-line-number = true
[metrics] [metrics]
exporter = "prometheus" exporter = "prometheus"

View File

@ -16,7 +16,6 @@ port = 6082
[database] [database]
type = "postgres" type = "postgres"
schema = "public"
namespace = "dc" namespace = "dc"
[[postgres]] [[postgres]]
@ -27,7 +26,7 @@ username = "postgres"
password = "G76hTg8T5Aa+SZQFc+0QnsRLo1UOjqpkp/jUQ+lySc8QCt4B" password = "G76hTg8T5Aa+SZQFc+0QnsRLo1UOjqpkp/jUQ+lySc8QCt4B"
[tracing] [tracing]
filter = "info,sqlx=warn,tower_http=warn" filter = "info,sqlx=warn"
[metrics] [metrics]
exporter = "prometheus" exporter = "prometheus"

View File

@ -69,9 +69,18 @@ pub trait RequestContext {
header.split(':').nth(3).map(|s| s.to_string()) header.split(':').nth(3).map(|s| s.to_string())
}) })
}); });
let mut ctx = Context::new(request_id); let mut ctx = Context::new(request_id);
ctx.set_trace_id(trace_id); ctx.set_trace_id(trace_id);
ctx.set_session_id(session_id); ctx.set_session_id(session_id);
// Emit metrics.
metrics::increment_gauge!("zino_http_requests_pending", 1.0);
metrics::increment_counter!(
"zino_http_requests_total",
"method" => self.request_method().to_string(),
);
ctx ctx
} }

View File

@ -336,10 +336,11 @@ impl From<Validation> for Response<http::StatusCode> {
impl From<Response<http::StatusCode>> for http::Response<Full<Bytes>> { impl From<Response<http::StatusCode>> for http::Response<Full<Bytes>> {
fn from(mut response: Response<http::StatusCode>) -> Self { fn from(mut response: Response<http::StatusCode>) -> Self {
let status_code = response.status_code;
let mut res = match response.content_type { let mut res = match response.content_type {
Some(ref content_type) => match serde_json::to_vec(&response.data) { Some(ref content_type) => match serde_json::to_vec(&response.data) {
Ok(bytes) => http::Response::builder() Ok(bytes) => http::Response::builder()
.status(response.status_code) .status(status_code)
.header(header::CONTENT_TYPE, content_type.as_ref()) .header(header::CONTENT_TYPE, content_type.as_ref())
.body(Full::from(bytes)) .body(Full::from(bytes))
.unwrap_or_default(), .unwrap_or_default(),
@ -357,7 +358,7 @@ impl From<Response<http::StatusCode>> for http::Response<Full<Bytes>> {
"application/problem+json" "application/problem+json"
}; };
http::Response::builder() http::Response::builder()
.status(response.status_code) .status(status_code)
.header(header::CONTENT_TYPE, content_type) .header(header::CONTENT_TYPE, content_type)
.body(Full::from(bytes)) .body(Full::from(bytes))
.unwrap_or_default() .unwrap_or_default()
@ -377,7 +378,8 @@ impl From<Response<http::StatusCode>> for http::Response<Full<Bytes>> {
res.headers_mut().insert("traceparent", header_value); res.headers_mut().insert("traceparent", header_value);
} }
response.record_server_timing("total", response.start_time.elapsed(), None); let duration = response.start_time.elapsed();
response.record_server_timing("total", duration, None);
if let Ok(header_value) = HeaderValue::try_from(response.server_timing.value().as_str()) { if let Ok(header_value) = HeaderValue::try_from(response.server_timing.value().as_str()) {
res.headers_mut().insert("server-timing", header_value); res.headers_mut().insert("server-timing", header_value);
} }
@ -388,6 +390,17 @@ impl From<Response<http::StatusCode>> for http::Response<Full<Bytes>> {
res.headers_mut().insert("x-request-id", header_value); res.headers_mut().insert("x-request-id", header_value);
} }
} }
// Emit metrics.
let labels = [("status", status_code.to_string())];
metrics::decrement_gauge!("zino_http_requests_pending", 1.0);
metrics::increment_counter!("zino_http_responses_total", &labels);
metrics::histogram!(
"zino_http_requests_duration_seconds",
duration.as_secs_f64(),
&labels,
);
res res
} }
} }

View File

@ -78,7 +78,7 @@ pub fn schema_macro(item: TokenStream) -> TokenStream {
} else if INTEGER_TYPES.contains(&type_name.as_str()) { } else if INTEGER_TYPES.contains(&type_name.as_str()) {
default_value = default_value.or_else(|| Some("0".to_string())); default_value = default_value.or_else(|| Some("0".to_string()));
} }
let quote_value = match default_value { let value_quote = match default_value {
Some(value) => { Some(value) => {
if value.contains("::") { if value.contains("::") {
if let Some((type_name, type_fn)) = value.split_once("::") { if let Some((type_name, type_fn)) = value.split_once("::") {
@ -94,12 +94,12 @@ pub fn schema_macro(item: TokenStream) -> TokenStream {
} }
None => quote! { None }, None => quote! { None },
}; };
let quote_index = match index_type { let index_quote = match index_type {
Some(index) => quote! { Some(#index) }, Some(index) => quote! { Some(#index) },
None => quote! { None }, None => quote! { None },
}; };
let column = quote! { let column = quote! {
zino_core::database::Column::new(#name, #type_name, #quote_value, #not_null, #quote_index) zino_core::database::Column::new(#name, #type_name, #value_quote, #not_null, #index_quote)
}; };
columns.push(column); columns.push(column);
} }

View File

@ -82,6 +82,8 @@ impl RequestContext for AxumExtractor<Request<Body>> {
} }
fn matched_path(&self) -> &str { fn matched_path(&self) -> &str {
// The `MatchedPath` extension is always accessible on handlers added via `Router::route`,
// but it is not accessible in middleware on nested routes.
if let Some(path) = self.extensions().get::<MatchedPath>() { if let Some(path) = self.extensions().get::<MatchedPath>() {
path.as_str() path.as_str()
} else { } else {
@ -90,11 +92,11 @@ impl RequestContext for AxumExtractor<Request<Body>> {
} }
fn original_uri(&self) -> &Uri { fn original_uri(&self) -> &Uri {
// The `OriginalUri` extension will always be present if using
// `Router` unless another extractor or middleware has removed it.
if let Some(original_uri) = self.extensions().get::<OriginalUri>() { if let Some(original_uri) = self.extensions().get::<OriginalUri>() {
&original_uri.0 &original_uri.0
} else { } else {
// The `OriginalUri` extension will always be present if using
// `Router` unless another extractor or middleware has removed it
self.uri() self.uri()
} }
} }