наблюдаемость: завершить базовый контур Community
Добавить структурированные журналы, метрики, трассировку и безопасный канал критических ошибок. Усилить границы рантайма, тесты, проверку зависимостей и сценарии развёртывания.
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
//! Закрытый семантический контракт spans Crank.
|
||||
//!
|
||||
//! Этот crate не настраивает subscriber и не знает об OTLP. Он ограничивает
|
||||
//! имена и атрибуты стадий статическим словарём, чтобы продуктовые crate не
|
||||
//! могли случайно экспортировать пользовательские данные.
|
||||
|
||||
use std::future::Future;
|
||||
|
||||
use tracing::{Instrument, Span, field::Empty, info_span};
|
||||
|
||||
macro_rules! stage_span {
|
||||
($name:literal) => {
|
||||
info_span!(
|
||||
target: "crank::trace",
|
||||
$name,
|
||||
outcome = Empty,
|
||||
error.category = Empty,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Stage {
|
||||
McpRateLimit,
|
||||
McpAccessCheck,
|
||||
McpCatalogLoad,
|
||||
McpToolsResolve,
|
||||
ApprovalCheck,
|
||||
RuntimeExecute,
|
||||
RuntimeArgumentsMap,
|
||||
RuntimeIdempotency,
|
||||
UpstreamHttp,
|
||||
RuntimeResponseTransform,
|
||||
AuthResolve,
|
||||
ApprovalRecovery,
|
||||
HistoryWrite,
|
||||
DbQuery,
|
||||
}
|
||||
|
||||
impl Stage {
|
||||
pub fn span(self) -> Span {
|
||||
match self {
|
||||
Self::McpRateLimit => stage_span!("mcp.rate_limit"),
|
||||
Self::McpAccessCheck => stage_span!("mcp.access.check"),
|
||||
Self::McpCatalogLoad => stage_span!("mcp.catalog.load"),
|
||||
Self::McpToolsResolve => stage_span!("mcp.tools.resolve"),
|
||||
Self::ApprovalCheck => stage_span!("approval.check"),
|
||||
Self::RuntimeExecute => stage_span!("runtime.execute"),
|
||||
Self::RuntimeArgumentsMap => stage_span!("runtime.arguments.map"),
|
||||
Self::RuntimeIdempotency => stage_span!("runtime.idempotency"),
|
||||
Self::UpstreamHttp => stage_span!("upstream.http"),
|
||||
Self::RuntimeResponseTransform => stage_span!("runtime.response.transform"),
|
||||
Self::AuthResolve => stage_span!("auth.resolve"),
|
||||
Self::ApprovalRecovery => stage_span!("approval.recovery"),
|
||||
Self::HistoryWrite => stage_span!("history.write"),
|
||||
Self::DbQuery => info_span!(
|
||||
target: "crank::trace",
|
||||
"db.query",
|
||||
outcome = Empty,
|
||||
error.category = Empty,
|
||||
db.system = "postgresql",
|
||||
db.operation = Empty,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn db_span(self, operation: DbOperation) -> Option<Span> {
|
||||
if self != Self::DbQuery {
|
||||
return None;
|
||||
}
|
||||
let span = self.span();
|
||||
span.record("db.operation", operation.as_str());
|
||||
Some(span)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn observe_db_query<T, E>(
|
||||
operation: DbOperation,
|
||||
future: impl Future<Output = Result<T, E>>,
|
||||
) -> Result<T, E> {
|
||||
let span = Stage::DbQuery
|
||||
.db_span(operation)
|
||||
.expect("database operation requires db.query stage");
|
||||
let result = future.instrument(span.clone()).await;
|
||||
match &result {
|
||||
Ok(_) => StageOutcome::Success.record(&span),
|
||||
Err(_) => {
|
||||
StageOutcome::Error.record(&span);
|
||||
ErrorCategory::Database.record(&span);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum StageOutcome {
|
||||
Success,
|
||||
Error,
|
||||
Allowed,
|
||||
Denied,
|
||||
Required,
|
||||
Replay,
|
||||
Execute,
|
||||
Skipped,
|
||||
CacheHit,
|
||||
}
|
||||
|
||||
impl StageOutcome {
|
||||
pub fn record(self, span: &Span) {
|
||||
span.record("outcome", self.as_str());
|
||||
}
|
||||
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Success => "success",
|
||||
Self::Error => "error",
|
||||
Self::Allowed => "allowed",
|
||||
Self::Denied => "denied",
|
||||
Self::Required => "required",
|
||||
Self::Replay => "replay",
|
||||
Self::Execute => "execute",
|
||||
Self::Skipped => "skipped",
|
||||
Self::CacheHit => "cache_hit",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ErrorCategory {
|
||||
Access,
|
||||
RateLimit,
|
||||
Catalog,
|
||||
Approval,
|
||||
Idempotency,
|
||||
Schema,
|
||||
Mapping,
|
||||
Upstream,
|
||||
Transformation,
|
||||
History,
|
||||
Database,
|
||||
Concurrency,
|
||||
Configuration,
|
||||
Internal,
|
||||
}
|
||||
|
||||
impl ErrorCategory {
|
||||
pub fn record(self, span: &Span) {
|
||||
span.record("error.category", self.as_str());
|
||||
}
|
||||
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Access => "access",
|
||||
Self::RateLimit => "rate_limit",
|
||||
Self::Catalog => "catalog",
|
||||
Self::Approval => "approval",
|
||||
Self::Idempotency => "idempotency",
|
||||
Self::Schema => "schema",
|
||||
Self::Mapping => "mapping",
|
||||
Self::Upstream => "upstream",
|
||||
Self::Transformation => "transformation",
|
||||
Self::History => "history",
|
||||
Self::Database => "database",
|
||||
Self::Concurrency => "concurrency",
|
||||
Self::Configuration => "configuration",
|
||||
Self::Internal => "internal",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum DbOperation {
|
||||
MachineAccessRead,
|
||||
MachineAccessTouch,
|
||||
CatalogLoad,
|
||||
ApprovalRead,
|
||||
ApprovalWrite,
|
||||
AuthProfileRead,
|
||||
SecretRead,
|
||||
SecretTouch,
|
||||
InvocationHistoryWrite,
|
||||
}
|
||||
|
||||
impl DbOperation {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::MachineAccessRead => "machine_access.read",
|
||||
Self::MachineAccessTouch => "machine_access.touch",
|
||||
Self::CatalogLoad => "catalog.load",
|
||||
Self::ApprovalRead => "approval.read",
|
||||
Self::ApprovalWrite => "approval.write",
|
||||
Self::AuthProfileRead => "auth_profile.read",
|
||||
Self::SecretRead => "secret.read",
|
||||
Self::SecretTouch => "secret.touch",
|
||||
Self::InvocationHistoryWrite => "invocation_history.write",
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user