feat: freeze typed metrics registry and bounded exemplars

This commit is contained in:
2026-08-14 11:52:00 +03:00
parent 17c8e3a8f0
commit 8e709acea9
29 changed files with 1690 additions and 103 deletions
@@ -3,7 +3,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use axum::{
Router,
body::{Body, to_bytes},
http::{Request, StatusCode},
http::{Request, StatusCode, header},
middleware,
routing::get,
};
@@ -14,8 +14,7 @@ use tower::ServiceExt;
#[tokio::test]
async fn http_metrics_use_matched_routes_and_closed_labels() {
let identity =
ServiceIdentity::try_new("metrics-test", "0.3.1", "test").expect("valid identity");
let identity = ServiceIdentity::try_new("admin-api", "0.3.1", "test").expect("valid identity");
let lifecycle = crank_observability::init(ObservabilityConfig::new(
identity,
"off",
@@ -32,7 +31,12 @@ async fn http_metrics_use_matched_routes_and_closed_labels() {
let app = Router::new()
.route(
"/api/admin/workspaces/{workspace_id}/operations/{operation_id}",
get(|| async { StatusCode::NO_CONTENT }),
get(|| async {
(
StatusCode::NO_CONTENT,
[("x-trace-id", "0123456789abcdef0123456789abcdef")],
)
}),
)
.layer(middleware::from_fn(record_http_request));
@@ -63,6 +67,7 @@ async fn http_metrics_use_matched_routes_and_closed_labels() {
assert_eq!(response.status(), StatusCode::NOT_FOUND);
let response = metrics
.clone()
.oneshot(
Request::get("/metrics")
.body(Body::empty())
@@ -82,6 +87,7 @@ async fn http_metrics_use_matched_routes_and_closed_labels() {
assert!(body.contains("method=\"GET\""));
assert!(body.contains("status_class=\"2xx\""));
assert!(body.contains("crank_http_request_duration_seconds_bucket"));
assert!(!body.contains("trace_id"));
assert!(!body.contains(sensitive_path_segment));
assert_eq!(
body.lines()
@@ -95,4 +101,30 @@ async fn http_metrics_use_matched_routes_and_closed_labels() {
1,
"different entity ids must not create additional series"
);
let openmetrics = metrics
.oneshot(
Request::get("/metrics")
.header(
header::ACCEPT,
"application/openmetrics-text; version=1.0.0",
)
.body(Body::empty())
.expect("request"),
)
.await
.expect("OpenMetrics response");
assert_eq!(openmetrics.status(), StatusCode::OK);
assert!(
openmetrics.headers()[header::CONTENT_TYPE]
.to_str()
.unwrap()
.starts_with("application/openmetrics-text")
);
let body = to_bytes(openmetrics.into_body(), 1024 * 1024)
.await
.unwrap();
let body = String::from_utf8(body.to_vec()).unwrap();
assert!(body.contains("# {trace_id=\"0123456789abcdef0123456789abcdef\"}"));
assert!(body.ends_with("# EOF\n"));
}
@@ -4,7 +4,7 @@ use crank_observability::{
fn config() -> ObservabilityConfig {
ObservabilityConfig::new(
ServiceIdentity::try_new("lifecycle-test", "0.3.1", "test").expect("valid test identity"),
ServiceIdentity::try_new("admin-api", "0.3.1", "test").expect("valid test identity"),
"info",
RedactionLimits::default(),
)
@@ -17,8 +17,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::test]
async fn typed_product_signals_are_exposed_by_a_real_scrape() {
let identity =
ServiceIdentity::try_new("metrics-signals", "0.3.1", "test").expect("valid identity");
let identity = ServiceIdentity::try_new("admin-api", "0.3.1", "test").expect("valid identity");
let lifecycle = crank_observability::init(ObservabilityConfig::new(
identity,
"off",
@@ -49,6 +48,7 @@ async fn typed_product_signals_are_exposed_by_a_real_scrape() {
McpMethod::ToolsCall,
McpResponseMode::Json,
McpOutcome::ToolError,
Duration::from_millis(1),
);
set_mcp_active_sessions(2);
let _stream = InFlightGuard::mcp_stream();
@@ -71,6 +71,18 @@ fn non_loopback_without_a_token_is_rejected_without_secret_data() {
assert!(!error.to_string().contains("token="));
}
#[test]
fn metrics_surface_rejects_an_unregistered_process_identity() {
let config = MetricsConfig::new(
true,
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9464),
None,
)
.unwrap();
let identity = ServiceIdentity::try_new("user-controlled", "0.3.1", "test").unwrap();
assert!(MetricsSurface::for_test(config, identity).is_err());
}
#[test]
fn schema_is_closed_and_uses_fixed_duration_buckets() {
let schema = metric_schema();