наблюдаемость: завершить базовый контур Community
CI / Rust Checks (push) Failing after 4m28s
CI / UI Checks (push) Has been skipped
CI / Frontend E2E (push) Has been skipped
CI / Community Image Smoke (push) Has been skipped
CI / Deploy (push) Has been skipped

Добавить структурированные журналы, метрики, трассировку и безопасный канал критических ошибок. Усилить границы рантайма, тесты, проверку зависимостей и сценарии развёртывания.
This commit is contained in:
2026-07-31 01:01:14 +03:00
parent 99bd05c145
commit 0e8f1ca03a
160 changed files with 13506 additions and 1499 deletions
@@ -0,0 +1,283 @@
use std::{env, net::SocketAddr};
use axum::{
Router,
extract::{Request, State},
http::{
HeaderMap, StatusCode,
header::{self, HeaderValue},
},
middleware::{self, Next},
response::{IntoResponse, Response},
routing::get,
};
use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle, PrometheusRecorder};
use sha2::{Digest, Sha256};
use subtle::ConstantTimeEq;
use thiserror::Error;
use tokio::net::TcpListener;
use crate::{DURATION_BUCKETS_SECONDS, ServiceIdentity};
const METRICS_ENABLED_ENV: &str = "CRANK_METRICS_ENABLED";
const METRICS_TOKEN_ENV: &str = "CRANK_METRICS_BEARER_TOKEN";
const PROMETHEUS_CONTENT_TYPE: &str = "text/plain; version=0.0.4; charset=utf-8";
#[derive(Clone)]
pub struct MetricsConfig {
enabled: bool,
bind_addr: SocketAddr,
token_digest: Option<[u8; 32]>,
}
impl std::fmt::Debug for MetricsConfig {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("MetricsConfig")
.field("enabled", &self.enabled)
.field("bind_addr", &self.bind_addr)
.field("authentication_configured", &self.token_digest.is_some())
.finish()
}
}
impl MetricsConfig {
pub fn new(
enabled: bool,
bind_addr: SocketAddr,
bearer_token: Option<String>,
) -> Result<Self, MetricsConfigError> {
let token_digest = bearer_token
.filter(|token| !token.is_empty())
.map(|token| token_digest(token.as_bytes()));
if enabled && !bind_addr.ip().is_loopback() && token_digest.is_none() {
return Err(MetricsConfigError::MissingTokenForExternalBind);
}
Ok(Self {
enabled,
bind_addr,
token_digest,
})
}
pub fn from_env(
bind_env: &'static str,
default_bind: SocketAddr,
) -> Result<Self, MetricsConfigError> {
let enabled = parse_enabled(env::var(METRICS_ENABLED_ENV))?;
let bind_addr = match env::var(bind_env) {
Ok(raw) => raw
.parse()
.map_err(|_| MetricsConfigError::InvalidBindAddress { field: bind_env })?,
Err(env::VarError::NotPresent) => default_bind,
Err(env::VarError::NotUnicode(_)) => {
return Err(MetricsConfigError::InvalidEnvironmentEncoding { field: bind_env });
}
};
let bearer_token = match env::var(METRICS_TOKEN_ENV) {
Ok(token) => Some(token),
Err(env::VarError::NotPresent) => None,
Err(env::VarError::NotUnicode(_)) => {
return Err(MetricsConfigError::InvalidEnvironmentEncoding {
field: METRICS_TOKEN_ENV,
});
}
};
Self::new(enabled, bind_addr, bearer_token)
}
pub fn enabled(&self) -> bool {
self.enabled
}
pub fn bind_addr(&self) -> SocketAddr {
self.bind_addr
}
pub fn requires_authentication(&self) -> bool {
!self.bind_addr.ip().is_loopback()
}
}
#[derive(Debug, Error)]
pub enum MetricsConfigError {
#[error("metrics environment variable is not valid UTF-8: {field}")]
InvalidEnvironmentEncoding { field: &'static str },
#[error("metrics bind address is invalid: {field}")]
InvalidBindAddress { field: &'static str },
#[error("metrics enabled flag must be one of true, false, 1, 0")]
InvalidEnabledFlag,
#[error("external metrics bind requires a bearer token")]
MissingTokenForExternalBind,
}
#[derive(Clone)]
struct MetricsState {
handle: PrometheusHandle,
token_digest: Option<[u8; 32]>,
requires_authentication: bool,
}
pub struct MetricsSurface {
config: MetricsConfig,
state: MetricsState,
_recorder: Option<PrometheusRecorder>,
}
impl MetricsSurface {
pub(crate) fn new(config: MetricsConfig, handle: PrometheusHandle) -> Self {
Self {
state: MetricsState {
handle,
token_digest: config.token_digest,
requires_authentication: config.requires_authentication(),
},
config,
_recorder: None,
}
}
pub fn for_test(
config: MetricsConfig,
identity: ServiceIdentity,
) -> Result<Self, MetricsSurfaceError> {
let recorder = prometheus_builder(&identity)?.build_recorder();
let handle = recorder.handle();
let mut surface = Self::new(config, handle);
surface._recorder = Some(recorder);
Ok(surface)
}
pub fn router(&self) -> Router {
Router::new()
.route("/metrics", get(render_metrics))
.route("/health", get(metrics_health))
.layer(middleware::from_fn_with_state(
self.state.clone(),
authorize_metrics,
))
.with_state(self.state.clone())
}
pub async fn bind(self) -> Result<MetricsServer, MetricsServeError> {
let listener = TcpListener::bind(self.config.bind_addr)
.await
.map_err(|_| MetricsServeError::Bind)?;
Ok(MetricsServer {
listener,
router: self.router(),
})
}
}
pub struct MetricsServer {
listener: TcpListener,
router: Router,
}
impl MetricsServer {
pub async fn serve(self) -> Result<(), MetricsServeError> {
axum::serve(self.listener, self.router)
.await
.map_err(|_| MetricsServeError::Serve)
}
}
#[derive(Debug, Error)]
pub enum MetricsSurfaceError {
#[error("failed to configure Prometheus recorder")]
RecorderConfiguration,
}
#[derive(Debug, Error)]
pub enum MetricsServeError {
#[error("failed to bind metrics listener")]
Bind,
#[error("metrics listener stopped unexpectedly")]
Serve,
}
pub(crate) fn install_prometheus_recorder(
identity: &ServiceIdentity,
) -> Result<PrometheusHandle, MetricsSurfaceError> {
prometheus_builder(identity)?
.install_recorder()
.map_err(|_| MetricsSurfaceError::RecorderConfiguration)
}
fn prometheus_builder(
identity: &ServiceIdentity,
) -> Result<PrometheusBuilder, MetricsSurfaceError> {
PrometheusBuilder::new()
.set_buckets(DURATION_BUCKETS_SECONDS)
.map(|builder| {
builder
.add_global_label("service", identity.service())
.add_global_label("version", identity.version())
.add_global_label("environment", identity.environment())
})
.map_err(|_| MetricsSurfaceError::RecorderConfiguration)
}
async fn render_metrics(State(state): State<MetricsState>) -> Response {
let mut response = state.handle.render().into_response();
response.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static(PROMETHEUS_CONTENT_TYPE),
);
response
}
async fn metrics_health() -> impl IntoResponse {
(StatusCode::OK, "ok\n")
}
async fn authorize_metrics(
State(state): State<MetricsState>,
request: Request,
next: Next,
) -> Response {
if !state.requires_authentication {
return next.run(request).await;
}
let authorized = bearer_token(request.headers())
.map(token_digest)
.zip(state.token_digest)
.is_some_and(|(actual, expected)| bool::from(actual.ct_eq(&expected)));
if authorized {
next.run(request).await
} else {
StatusCode::UNAUTHORIZED.into_response()
}
}
fn bearer_token(headers: &HeaderMap) -> Option<&[u8]> {
headers
.get(header::AUTHORIZATION)?
.as_bytes()
.strip_prefix(b"Bearer ")
.filter(|token| !token.is_empty())
}
fn token_digest(token: &[u8]) -> [u8; 32] {
Sha256::digest(token).into()
}
fn parse_enabled(value: Result<String, env::VarError>) -> Result<bool, MetricsConfigError> {
match value {
Ok(raw) => match raw.to_ascii_lowercase().as_str() {
"true" | "1" => Ok(true),
"false" | "0" => Ok(false),
_ => Err(MetricsConfigError::InvalidEnabledFlag),
},
Err(env::VarError::NotPresent) => Ok(true),
Err(env::VarError::NotUnicode(_)) => Err(MetricsConfigError::InvalidEnvironmentEncoding {
field: METRICS_ENABLED_ENV,
}),
}
}