наблюдаемость: завершить базовый контур Community
Добавить структурированные журналы, метрики, трассировку и безопасный канал критических ошибок. Усилить границы рантайма, тесты, проверку зависимостей и сценарии развёртывания.
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
|
||||
use axum::{
|
||||
body::{Body, to_bytes},
|
||||
http::{Request, StatusCode, header},
|
||||
};
|
||||
use crank_observability::{
|
||||
DURATION_BUCKETS_SECONDS, MetricsConfig, MetricsConfigError, MetricsSurface, ServiceIdentity,
|
||||
metric_schema,
|
||||
};
|
||||
use tower::ServiceExt;
|
||||
|
||||
fn identity() -> ServiceIdentity {
|
||||
ServiceIdentity::try_new("admin-api", "0.3.1", "test").expect("valid identity")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loopback_is_allowed_without_a_token() {
|
||||
let config = MetricsConfig::new(
|
||||
true,
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9464),
|
||||
None,
|
||||
)
|
||||
.expect("loopback metrics must be safe by default");
|
||||
|
||||
assert_eq!(config.bind_addr().to_string(), "127.0.0.1:9464");
|
||||
assert!(!config.requires_authentication());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_loopback_without_a_token_is_rejected_without_secret_data() {
|
||||
let error = MetricsConfig::new(
|
||||
true,
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9464),
|
||||
None,
|
||||
)
|
||||
.expect_err("external metrics must require authentication");
|
||||
|
||||
assert!(matches!(
|
||||
error,
|
||||
MetricsConfigError::MissingTokenForExternalBind
|
||||
));
|
||||
assert!(!error.to_string().contains("token="));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schema_is_closed_and_uses_fixed_duration_buckets() {
|
||||
let schema = metric_schema();
|
||||
let names: Vec<_> = schema.iter().map(|metric| metric.name).collect();
|
||||
|
||||
assert!(names.contains(&"crank_http_requests_total"));
|
||||
assert!(names.contains(&"crank_http_request_duration_seconds"));
|
||||
assert!(names.contains(&"crank_mcp_requests_total"));
|
||||
assert!(names.contains(&"crank_tool_invocations_total"));
|
||||
assert!(names.contains(&"crank_runtime_inflight"));
|
||||
assert!(names.contains(&"crank_db_pool_connections"));
|
||||
assert!(names.contains(&"crank_catalog_tools"));
|
||||
assert!(names.contains(&"crank_invocation_history_lost_total"));
|
||||
assert!(names.contains(&"crank_telemetry_export_failures_total"));
|
||||
|
||||
for metric in schema {
|
||||
for forbidden in [
|
||||
"workspace",
|
||||
"agent_id",
|
||||
"operation_id",
|
||||
"request_id",
|
||||
"url",
|
||||
"error_message",
|
||||
"text",
|
||||
] {
|
||||
assert!(
|
||||
!metric.labels.contains(&forbidden),
|
||||
"{} exposes forbidden label {forbidden}",
|
||||
metric.name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
DURATION_BUCKETS_SECONDS,
|
||||
&[
|
||||
0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn external_surface_protects_both_routes_and_exposes_nothing_else() {
|
||||
let token = "metrics-canary-secret";
|
||||
let config = MetricsConfig::new(
|
||||
true,
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9464),
|
||||
Some(token.to_owned()),
|
||||
)
|
||||
.expect("external metrics with token");
|
||||
let surface = MetricsSurface::for_test(config, identity()).expect("test metrics surface");
|
||||
let app = surface.router();
|
||||
|
||||
for path in ["/metrics", "/health"] {
|
||||
let unauthorized = app
|
||||
.clone()
|
||||
.oneshot(Request::get(path).body(Body::empty()).expect("request"))
|
||||
.await
|
||||
.expect("response");
|
||||
assert_eq!(unauthorized.status(), StatusCode::UNAUTHORIZED);
|
||||
|
||||
let authorized = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::get(path)
|
||||
.header(header::AUTHORIZATION, format!("Bearer {token}"))
|
||||
.body(Body::empty())
|
||||
.expect("request"),
|
||||
)
|
||||
.await
|
||||
.expect("response");
|
||||
assert_eq!(authorized.status(), StatusCode::OK);
|
||||
let body = to_bytes(authorized.into_body(), 1024 * 1024)
|
||||
.await
|
||||
.expect("bounded body");
|
||||
assert!(!String::from_utf8_lossy(&body).contains(token));
|
||||
}
|
||||
|
||||
let absent = app
|
||||
.oneshot(
|
||||
Request::get("/api/operations")
|
||||
.header(header::AUTHORIZATION, format!("Bearer {token}"))
|
||||
.body(Body::empty())
|
||||
.expect("request"),
|
||||
)
|
||||
.await
|
||||
.expect("response");
|
||||
assert_eq!(absent.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
Reference in New Issue
Block a user