0e8f1ca03a
Добавить структурированные журналы, метрики, трассировку и безопасный канал критических ошибок. Усилить границы рантайма, тесты, проверку зависимостей и сценарии развёртывания.
107 lines
3.3 KiB
Rust
107 lines
3.3 KiB
Rust
use std::sync::{Arc, Mutex};
|
|
|
|
use crank_trace::{DbOperation, ErrorCategory, Stage, StageOutcome};
|
|
use tracing::{Id, Subscriber, field::Visit};
|
|
use tracing_subscriber::{Layer, layer::SubscriberExt, registry::LookupSpan};
|
|
|
|
#[test]
|
|
fn stage_names_and_attributes_are_closed() {
|
|
let captured = Arc::new(Mutex::new(Vec::new()));
|
|
let subscriber = tracing_subscriber::registry().with(CaptureLayer(Arc::clone(&captured)));
|
|
|
|
tracing::subscriber::with_default(subscriber, || {
|
|
let span = Stage::RuntimeExecute.span();
|
|
StageOutcome::Success.record(&span);
|
|
ErrorCategory::Mapping.record(&span);
|
|
drop(span);
|
|
|
|
let db_span = Stage::DbQuery
|
|
.db_span(DbOperation::InvocationHistoryWrite)
|
|
.expect("db stage accepts a db operation");
|
|
StageOutcome::Error.record(&db_span);
|
|
drop(db_span);
|
|
});
|
|
|
|
let spans = captured.lock().expect("captured spans");
|
|
assert_eq!(spans[0].name, "runtime.execute");
|
|
assert_eq!(spans[0].fields["outcome"], "success");
|
|
assert_eq!(spans[0].fields["error.category"], "mapping");
|
|
assert_eq!(spans[1].name, "db.query");
|
|
assert_eq!(spans[1].fields["db.system"], "postgresql");
|
|
assert_eq!(spans[1].fields["db.operation"], "invocation_history.write");
|
|
}
|
|
|
|
#[test]
|
|
fn non_database_stage_rejects_database_attributes() {
|
|
assert!(
|
|
Stage::RuntimeExecute
|
|
.db_span(DbOperation::CatalogLoad)
|
|
.is_none()
|
|
);
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct CaptureLayer(Arc<Mutex<Vec<CapturedSpan>>>);
|
|
|
|
#[derive(Debug)]
|
|
struct CapturedSpan {
|
|
name: &'static str,
|
|
fields: std::collections::BTreeMap<String, String>,
|
|
}
|
|
|
|
impl<S> Layer<S> for CaptureLayer
|
|
where
|
|
S: Subscriber + for<'lookup> LookupSpan<'lookup>,
|
|
{
|
|
fn on_new_span(
|
|
&self,
|
|
attributes: &tracing::span::Attributes<'_>,
|
|
id: &Id,
|
|
context: tracing_subscriber::layer::Context<'_, S>,
|
|
) {
|
|
let mut visitor = FieldVisitor::default();
|
|
attributes.record(&mut visitor);
|
|
context
|
|
.span(id)
|
|
.expect("span exists")
|
|
.extensions_mut()
|
|
.insert(self.0.lock().expect("capture lock").len());
|
|
self.0.lock().expect("capture lock").push(CapturedSpan {
|
|
name: attributes.metadata().name(),
|
|
fields: visitor.fields,
|
|
});
|
|
}
|
|
|
|
fn on_record(
|
|
&self,
|
|
id: &Id,
|
|
values: &tracing::span::Record<'_>,
|
|
context: tracing_subscriber::layer::Context<'_, S>,
|
|
) {
|
|
let span = context.span(id).expect("span exists");
|
|
let index = *span.extensions().get::<usize>().expect("capture index");
|
|
let mut visitor = FieldVisitor::default();
|
|
values.record(&mut visitor);
|
|
self.0.lock().expect("capture lock")[index]
|
|
.fields
|
|
.extend(visitor.fields);
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct FieldVisitor {
|
|
fields: std::collections::BTreeMap<String, String>,
|
|
}
|
|
|
|
impl Visit for FieldVisitor {
|
|
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
|
|
self.fields
|
|
.insert(field.name().to_owned(), value.to_owned());
|
|
}
|
|
|
|
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
|
|
self.fields
|
|
.insert(field.name().to_owned(), format!("{value:?}"));
|
|
}
|
|
}
|