use std::ops::Deref;
use std::ops::DerefMut;
use std::time::SystemTime;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use super::Result;
use super::SpanContext;
pub mod log;
pub mod tag;
use self::log::Log;
use self::tag::SpanTags;
use self::tag::TagValue;
#[derive(Debug)]
pub struct AutoFinishingSpan(Option<Span>);
impl AutoFinishingSpan {
pub(crate) fn new(span: Span) -> AutoFinishingSpan {
AutoFinishingSpan(Some(span))
}
}
impl AutoFinishingSpan {
pub fn context(&self) -> &SpanContext {
self.0.as_ref().unwrap().context()
}
pub fn log(&mut self, log: Log) {
self.0.as_mut().unwrap().log(log);
}
}
impl AsMut<Span> for AutoFinishingSpan {
fn as_mut(&mut self) -> &mut Span {
self.0.as_mut().unwrap()
}
}
impl Deref for AutoFinishingSpan {
type Target = Span;
fn deref(&self) -> &Span {
self.0.as_ref().unwrap()
}
}
impl DerefMut for AutoFinishingSpan {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0.as_mut().unwrap()
}
}
impl Drop for AutoFinishingSpan {
fn drop(&mut self) {
if let Some(span) = self.0.take() {
let _ = span.finish();
}
}
}
#[derive(Debug)]
pub struct FinishedSpan {
context: SpanContext,
finish_time: SystemTime,
logs: Vec<Log>,
name: String,
references: Vec<SpanReference>,
start_time: SystemTime,
tags: SpanTags,
}
impl FinishedSpan {
pub fn context(&self) -> &SpanContext {
&self.context
}
pub fn finish_time(&self) -> &SystemTime {
&self.finish_time
}
pub fn logs(&self) -> &Vec<Log> {
&self.logs
}
pub fn name(&self) -> &String {
&self.name
}
pub fn references(&self) -> &Vec<SpanReference> {
&self.references
}
pub fn start_time(&self) -> &SystemTime {
&self.start_time
}
pub fn tags(&self) -> &SpanTags {
&self.tags
}
}
#[derive(Debug)]
pub struct Span {
context: SpanContext,
finish_time: Option<SystemTime>,
logs: Vec<Log>,
name: String,
references: Vec<SpanReference>,
sender: SpanSender,
start_time: SystemTime,
tags: SpanTags,
}
impl Span {
pub fn new(
name: &str, context: SpanContext, options: StartOptions,
sender: SpanSender
) -> Span {
let mut span = Span {
context,
finish_time: None,
logs: Vec::new(),
name: String::from(name),
references: Vec::new(),
sender,
start_time: options.start_time.unwrap_or_else(SystemTime::now),
tags: SpanTags::new(),
};
for reference in options.references {
span.reference_span(reference);
}
span
}
}
impl Span {
pub fn auto_finish(self) -> AutoFinishingSpan {
AutoFinishingSpan::new(self)
}
pub fn child_of(&mut self, parent: SpanContext) {
self.reference_span(SpanReference::ChildOf(parent));
}
pub fn context(&self) -> &SpanContext {
&self.context
}
pub fn finish_time(&mut self, finish_time: SystemTime) {
self.finish_time = Some(finish_time);
}
pub fn finish(self) -> Result<()> {
let finished = FinishedSpan {
context: self.context,
finish_time: self.finish_time.unwrap_or_else(SystemTime::now),
logs: self.logs,
name: self.name,
references: self.references,
start_time: self.start_time,
tags: self.tags,
};
self.sender.send(finished)?;
Ok(())
}
pub fn follows(&mut self, parent: SpanContext) {
self.reference_span(SpanReference::FollowsFrom(parent));
}
pub fn get_baggage_item(&self, key: &str) -> Option<&String> {
self.context.get_baggage_item(key)
}
pub fn log(&mut self, mut log: Log) {
log.at_or_now();
self.logs.push(log);
}
pub fn operation_name(&self) -> &str {
&self.name
}
pub fn reference_span(&mut self, reference: SpanReference) {
self.context.reference_span(&reference);
match reference {
SpanReference::ChildOf(ref parent) |
SpanReference::FollowsFrom(ref parent) => {
for (key, value) in parent.baggage_items() {
self.context.set_baggage_item(key.clone(), value.clone())
}
}
}
self.references.push(reference);
}
pub fn references(&self) -> &[SpanReference] {
&self.references
}
pub fn set_baggage_item(&mut self, key: &str, value: &str) {
self.context.set_baggage_item(String::from(key), String::from(value));
}
pub fn set_operation_name(&mut self, name: &str) {
self.name = String::from(name);
}
pub fn tag<TV: Into<TagValue>>(&mut self, tag: &str, value: TV) {
self.tags.tag(tag, value.into());
}
}
impl AsMut<Span> for Span {
fn as_mut(&mut self) -> &mut Span {
self
}
}
#[derive(Clone, Debug)]
pub enum SpanReference {
ChildOf(SpanContext),
FollowsFrom(SpanContext)
}
pub type SpanReceiver = Receiver<FinishedSpan>;
pub type SpanSender = Sender<FinishedSpan>;
pub struct StartOptions {
references: Vec<SpanReference>,
start_time: Option<SystemTime>,
}
impl StartOptions {
pub fn child_of(self, parent: SpanContext) -> Self {
self.reference_span(SpanReference::ChildOf(parent))
}
pub fn follows(self, parent: SpanContext) -> Self {
self.reference_span(SpanReference::FollowsFrom(parent))
}
pub fn reference_span(mut self, reference: SpanReference) -> Self {
self.references.push(reference);
self
}
pub fn start_time(mut self, start_time: SystemTime) -> Self {
self.start_time = Some(start_time);
self
}
}
impl Default for StartOptions {
fn default() -> StartOptions {
StartOptions {
references: Vec::new(),
start_time: None,
}
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use crossbeam_channel::unbounded;
use super::super::ImplContextBox;
use super::super::SpanContext;
use super::super::SpanReferenceAware;
use super::super::StartOptions;
use super::AutoFinishingSpan;
use super::FinishedSpan;
use super::Span;
use super::SpanReceiver;
use super::SpanReference;
#[derive(Debug, Clone)]
struct TestContext {
pub id: String
}
impl TestContext {
fn new(options: StartOptions) -> (Span, SpanReceiver) {
let (sender, receiver) = unbounded();
let context = SpanContext::new(ImplContextBox::new(TestContext {
id: String::from("test-id")
}));
(Span::new("test-span", context, options, sender), receiver)
}
}
impl SpanReferenceAware for TestContext {
fn reference_span(&mut self, _: &SpanReference) {}
}
#[test]
fn autoclose_finish_span_on_drop() {
let options = StartOptions::default();
let (span, receiver) = TestContext::new(options);
{
span.auto_finish();
}
receiver.recv_timeout(Duration::from_secs(1)).unwrap();
}
#[test]
fn autofinish_as_mut() {
let options = StartOptions::default();
let (span, _reciver) = TestContext::new(options);
let mut span: AutoFinishingSpan = span.auto_finish();
let span_ref: &mut Span = span.as_mut();
span_ref.tag("key", "value");
}
#[test]
fn autofinish_deref() {
let options = StartOptions::default();
let (span, _reciver) = TestContext::new(options);
let mut span: AutoFinishingSpan = span.auto_finish();
{
let _span_ref: &Span = &span;
}
{
let _mut_ref: &mut Span = &mut span;
}
}
#[test]
fn start_span_on_creation() {
let (_span, _): (Span, _) = TestContext::new(StartOptions::default());
}
#[test]
fn send_span_on_finish() {
let (sender, receiver) = unbounded();
let context = SpanContext::new(ImplContextBox::new(TestContext {
id: String::from("test-id")
}));
let options = StartOptions::default();
let span: Span = Span::new("test-span", context, options, sender);
span.finish().unwrap();
let _finished: FinishedSpan = receiver.recv().unwrap();
}
#[test]
fn set_span_name() {
let (sender, _) = unbounded();
let context = SpanContext::new(ImplContextBox::new(TestContext {
id: String::from("test-id")
}));
let options = StartOptions::default();
let mut span = Span::new("test-span", context, options, sender);
span.set_operation_name("some-other-name");
assert_eq!("some-other-name", span.operation_name());
}
#[test]
fn span_child_of_another() {
let (sender, _) = unbounded();
let context = SpanContext::new(ImplContextBox::new(TestContext {
id: String::from("test-id-1")
}));
let options = StartOptions::default();
let mut span = Span::new("test-span", context, options, sender);
let mut context = SpanContext::new(ImplContextBox::new(TestContext {
id: String::from("test-id-2")
}));
context.set_baggage_item(String::from("a"), String::from("b"));
span.child_of(context.clone());
match span.references().get(0).unwrap() {
&SpanReference::ChildOf(ref context) => {
let span = context.impl_context::<TestContext>().unwrap();
assert_eq!(span.id, "test-id-2");
},
_ => panic!("Invalid span reference")
}
let item = span.get_baggage_item("a").unwrap();
assert_eq!(item, "b");
}
#[test]
fn span_follows_another() {
let (sender, _) = unbounded();
let context = SpanContext::new(ImplContextBox::new(TestContext {
id: String::from("test-id-1")
}));
let options = StartOptions::default();
let mut span = Span::new("test-span", context, options, sender);
let mut context = SpanContext::new(ImplContextBox::new(TestContext {
id: String::from("test-id-2")
}));
context.set_baggage_item(String::from("a"), String::from("b"));
span.follows(context.clone());
match span.references().get(0).unwrap() {
&SpanReference::FollowsFrom(ref context) => {
let span = context.impl_context::<TestContext>().unwrap();
assert_eq!(span.id, "test-id-2");
},
_ => panic!("Invalid span reference")
}
let item = span.get_baggage_item("a").unwrap();
assert_eq!(item, "b");
}
mod references {
use super::super::super::ImplContextBox;
use super::super::SpanContext;
use super::super::SpanReference;
use super::super::StartOptions;
use super::TestContext;
#[test]
fn child_of() {
let parent = SpanContext::new(ImplContextBox::new(TestContext {
id: String::from("test-id")
}));
let options = StartOptions::default()
.child_of(parent);
let (span, _) = TestContext::new(options);
match span.references().get(0) {
Some(&SpanReference::ChildOf(_)) => (),
Some(_) => panic!("Invalid span reference"),
None => panic!("Missing span reference")
}
}
#[test]
fn follows() {
let parent = SpanContext::new(ImplContextBox::new(TestContext {
id: String::from("test-id")
}));
let options = StartOptions::default()
.follows(parent);
let (span, _) = TestContext::new(options);
match span.references().get(0) {
Some(&SpanReference::FollowsFrom(_)) => (),
Some(_) => panic!("Invalid span reference"),
None => panic!("Missing span reference")
}
}
#[test]
fn multi_refs() {
let parent = SpanContext::new(ImplContextBox::new(TestContext {
id: String::from("test-id")
}));
let options = StartOptions::default()
.child_of(parent.clone())
.follows(parent);
let (span, _) = TestContext::new(options);
match span.references().get(0) {
Some(&SpanReference::ChildOf(_)) => (),
Some(_) => panic!("Invalid span reference"),
None => panic!("Missing span reference")
}
match span.references().get(1) {
Some(&SpanReference::FollowsFrom(_)) => (),
Some(_) => panic!("Invalid span reference"),
None => panic!("Missing span reference")
}
}
}
mod logs {
}
mod tags {
use super::super::StartOptions;
use super::super::TagValue;
use super::TestContext;
#[test]
fn add_generic_tag() {
let (mut span, receiver) = TestContext::new(StartOptions::default());
span.tag("key", TagValue::String(String::from("value")));
span.finish().unwrap();
let span = receiver.recv().unwrap();
match span.tags().get("key") {
Some(&TagValue::String(ref v)) => assert_eq!(v, "value"),
Some(_) => panic!("Invalid tag type"),
None => panic!("Tag not found")
}
}
#[test]
fn add_bool_tag() {
let (mut span, receiver) = TestContext::new(StartOptions::default());
span.tag("key", true);
span.finish().unwrap();
let span = receiver.recv().unwrap();
match span.tags().get("key") {
Some(&TagValue::Boolean(v)) => assert_eq!(v, true),
Some(_) => panic!("Invalid tag type"),
None => panic!("Tag not found")
}
}
#[test]
fn add_float_tag() {
let (mut span, receiver) = TestContext::new(StartOptions::default());
span.tag("key", 1.2);
span.finish().unwrap();
let span = receiver.recv().unwrap();
match span.tags().get("key") {
Some(&TagValue::Float(v)) => assert_eq!(v, 1.2),
Some(_) => panic!("Invalid tag type"),
None => panic!("Tag not found")
}
}
#[test]
fn add_integer_tag() {
let (mut span, receiver) = TestContext::new(StartOptions::default());
span.tag("key", -2);
span.finish().unwrap();
let span = receiver.recv().unwrap();
match span.tags().get("key") {
Some(&TagValue::Integer(v)) => assert_eq!(v, -2),
Some(_) => panic!("Invalid tag type"),
None => panic!("Tag not found")
}
}
#[test]
fn add_str_tag() {
let (mut span, receiver) = TestContext::new(StartOptions::default());
span.tag("key", "value");
span.finish().unwrap();
let span = receiver.recv().unwrap();
match span.tags().get("key") {
Some(&TagValue::String(ref v)) => assert_eq!(v, "value"),
Some(_) => panic!("Invalid tag type"),
None => panic!("Tag not found")
}
}
#[test]
fn add_string_tag() {
let (mut span, receiver) = TestContext::new(StartOptions::default());
span.tag("key", String::from("value"));
span.finish().unwrap();
let span = receiver.recv().unwrap();
match span.tags().get("key") {
Some(&TagValue::String(ref v)) => assert_eq!(v, "value"),
Some(_) => panic!("Invalid tag type"),
None => panic!("Tag not found")
}
}
}
mod times {
use std::time::Duration;
use std::time::SystemTime;
use super::super::StartOptions;
use super::TestContext;
#[test]
fn finish_span_on_finish() {
let about_now = SystemTime::now();
let options = StartOptions::default();
let (span, receiver) = TestContext::new(options);
let about_soon = about_now + Duration::from_secs(600);
span.finish().unwrap();
let span = receiver.recv().unwrap();
assert!(about_now <= span.finish_time, "Finish time too old");
assert!(span.finish_time <= about_soon, "Finish time too new");
}
#[test]
fn finish_span_at_finish_time() {
let in_ten_minutes = SystemTime::now() + Duration::from_secs(600);
let options = StartOptions::default();
let (mut span, receiver) = TestContext::new(options);
span.finish_time(in_ten_minutes);
span.finish().unwrap();
let span = receiver.recv().unwrap();
assert_eq!(span.finish_time, in_ten_minutes);
}
#[test]
fn starts_now_by_default() {
let about_now = SystemTime::now();
let options = StartOptions::default();
let (span, _) = TestContext::new(options);
let about_soon = about_now + Duration::from_secs(600);
assert!(about_now <= span.start_time, "Start time too old");
assert!(span.start_time <= about_soon, "Start time too new");
}
#[test]
fn start_time_set() {
let ten_minutes_ago = SystemTime::now() - Duration::from_secs(600);
let options = StartOptions::default()
.start_time(ten_minutes_ago.clone());
let (span, _) = TestContext::new(options);
assert_eq!(span.start_time, ten_minutes_ago);
}
}
}