use super::init::{create_tracer_and_span_rx, TracingHarness};
use super::internal::Tracer;
use crate::telemetry::scope::Scope;
use crate::telemetry::settings::TracingSettings;
use cf_rustracing::span::SpanReference;
use cf_rustracing::tag::TagValue;
use cf_rustracing_jaeger::span::{FinishedSpan, SpanReceiver};
use std::collections::HashMap;
use std::iter::{FusedIterator, Iterator};
use std::time::SystemTime;
type ParentId = Option<u64>;
#[derive(Debug, PartialEq)]
pub struct TestTrace(pub TestSpan);
impl TestTrace {
pub fn iter(&self) -> TestTraceIterator {
TestTraceIterator {
stack: vec![&self.0],
}
}
}
pub struct TestTraceIterator<'s> {
stack: Vec<&'s TestSpan>,
}
impl<'s> Iterator for TestTraceIterator<'s> {
type Item = &'s TestSpan;
fn next(&mut self) -> Option<Self::Item> {
let span = self.stack.pop();
if let Some(span) = span {
self.stack.extend(span.children.iter().rev());
}
span
}
}
impl<'s> FusedIterator for TestTraceIterator<'s> {}
#[derive(Debug, PartialEq)]
pub struct TestSpan {
pub name: String,
pub children: Vec<TestSpan>,
pub logs: Vec<(String, String)>,
pub tags: Vec<(String, TagValue)>,
pub start_time: SystemTime,
pub finish_time: SystemTime,
}
#[derive(Default, Copy, Clone)]
pub struct TestTraceOptions {
pub include_logs: bool,
pub include_tags: bool,
pub include_start_time: bool,
pub include_finish_time: bool,
}
#[must_use]
pub(crate) struct TestTracerScope {
_inner: Scope<Tracer>,
}
impl TestTracerScope {
#[inline]
pub(crate) fn new(tracer: Tracer) -> Self {
Self {
_inner: Scope::new(&TracingHarness::get().test_tracer_scope_stack, tracer),
}
}
}
pub(crate) struct TestTracesSink {
span_rx: SpanReceiver,
raw_spans: HashMap<ParentId, Vec<FinishedSpan>>,
}
impl TestTracesSink {
pub(crate) fn traces(&mut self, options: TestTraceOptions) -> Vec<TestTrace> {
while let Ok(span) = self.span_rx.try_recv() {
add_raw_span(span, &mut self.raw_spans);
}
for spans in self.raw_spans.values_mut() {
spans.sort_by_key(FinishedSpan::start_time);
}
match self.raw_spans.get(&None) {
Some(roots) => roots
.iter()
.map(|root| TestTrace(create_test_span(root, &self.raw_spans, options)))
.collect(),
None => vec![],
}
}
}
fn add_raw_span(span: FinishedSpan, raw_spans: &mut HashMap<ParentId, Vec<FinishedSpan>>) {
let parent_id = span.references().iter().find_map(|r| match r {
SpanReference::ChildOf(parent) => Some(parent.span_id()),
_ => None,
});
raw_spans.entry(parent_id).or_default().push(span)
}
fn create_test_span(
raw_span: &FinishedSpan,
raw_spans: &HashMap<ParentId, Vec<FinishedSpan>>,
options: TestTraceOptions,
) -> TestSpan {
let span_id = raw_span.context().state().span_id();
TestSpan {
name: raw_span.operation_name().to_string(),
children: match raw_spans.get(&Some(span_id)) {
Some(raw_children) => raw_children
.iter()
.map(|c| create_test_span(c, raw_spans, options))
.collect(),
None => vec![],
},
logs: if options.include_logs {
span_logs(raw_span)
} else {
vec![]
},
tags: if options.include_tags {
span_tags(raw_span)
} else {
vec![]
},
start_time: if options.include_start_time {
raw_span.start_time()
} else {
SystemTime::UNIX_EPOCH
},
finish_time: if options.include_finish_time {
raw_span.finish_time()
} else {
SystemTime::UNIX_EPOCH
},
}
}
fn span_logs(raw_span: &FinishedSpan) -> Vec<(String, String)> {
raw_span
.logs()
.iter()
.flat_map(|l| l.fields().iter())
.map(|f| (f.name().to_string(), f.value().to_string()))
.collect()
}
fn span_tags(raw_span: &FinishedSpan) -> Vec<(String, TagValue)> {
raw_span
.tags()
.iter()
.map(|t| (t.name().to_string(), t.value().clone()))
.collect()
}
pub(crate) fn current_test_tracer() -> Option<Tracer> {
TracingHarness::get().test_tracer_scope_stack.current()
}
pub(crate) fn create_test_tracer(settings: &TracingSettings) -> (Tracer, TestTracesSink) {
let (tracer, span_rx) =
create_tracer_and_span_rx(settings).expect("should create tracer with default settings");
let sink = TestTracesSink {
span_rx,
raw_spans: Default::default(),
};
(tracer, sink)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::telemetry::tracing::test_trace;
use std::thread;
use std::time::Duration;
fn make_test_spans(tracer: &Tracer) {
let root1 = tracer.span("root1").start();
let root1_child1 = root1.child("root1_child1", |o| o.start());
let root1_child1_1 = root1_child1.child("root1_child1_1", |o| o.start());
let root1_child1_2 = root1_child1.child("root1_child1_2", |o| o.start());
drop(root1_child1_1);
thread::sleep(Duration::from_millis(10));
drop(root1_child1_2);
let root1_child2 = root1.child("root1_child2", |o| o.start());
drop(root1_child1);
thread::sleep(Duration::from_millis(10));
drop(root1_child2);
let root2 = tracer.span("root2").start();
let _root2_child1 = root2.child("root2_child1", |o| o.start());
}
#[tokio::test]
async fn span_tree() {
let (tracer, mut sink) = create_test_tracer(&Default::default());
make_test_spans(&tracer);
assert_eq!(
sink.traces(Default::default()),
vec![
test_trace! {
"root1" => {
"root1_child1" => {
"root1_child1_1",
"root1_child1_2"
},
"root1_child2"
}
},
test_trace! {
"root2" => {
"root2_child1"
}
}
]
);
}
#[tokio::test]
async fn span_iterator() {
let (tracer, mut sink) = create_test_tracer(&Default::default());
make_test_spans(&tracer);
let root1_spans: Vec<_> = sink.traces(Default::default())[0]
.iter()
.map(|s| s.name.clone())
.collect();
assert_eq!(
root1_spans,
vec![
"root1",
"root1_child1",
"root1_child1_1",
"root1_child1_2",
"root1_child2"
]
);
}
}