From 2715bccc609a24f897aadbfe37bcb859e9ea3aa0 Mon Sep 17 00:00:00 2001 From: Michael Nitschinger Date: Tue, 13 Mar 2018 14:09:23 +0100 Subject: [PATCH] WIP: Span still todo: - add LogValue - implement log - implement finish - add docs to everything once done --- opentracing-api/src/lib.rs | 8 +- opentracing-api/src/reference.rs | 102 +++++++++++++++++++++ opentracing-api/src/span.rs | 83 +++++++++++++++++ opentracing-api/src/tag.rs | 20 ++++ opentracing-mock/src/lib.rs | 151 ++++++++++++++++++++++++++++++- opentracing-noop/src/lib.rs | 84 ++++++++++++++++- 6 files changed, 441 insertions(+), 7 deletions(-) create mode 100644 opentracing-api/src/reference.rs create mode 100644 opentracing-api/src/span.rs diff --git a/opentracing-api/src/lib.rs b/opentracing-api/src/lib.rs index ac6ec50..9ec1a45 100644 --- a/opentracing-api/src/lib.rs +++ b/opentracing-api/src/lib.rs @@ -1,9 +1,13 @@ #![doc(html_root_url = "https://docs.rs/opentracing-api/0.1.0")] mod context; -mod tag; mod field; +mod reference; +mod span; +mod tag; pub use context::SpanContext; -pub use tag::{ParseTagsError, Tags}; pub use field::{Fields, ParseFieldsError}; +pub use reference::{ParseReferencesError, References}; +pub use span::{FinishedSpan, Span}; +pub use tag::{ParseTagsError, TagValue, Tags}; diff --git a/opentracing-api/src/reference.rs b/opentracing-api/src/reference.rs new file mode 100644 index 0000000..0f077a9 --- /dev/null +++ b/opentracing-api/src/reference.rs @@ -0,0 +1,102 @@ +use std::fmt; +use std::error::Error; +use std::str::FromStr; + +const REF_CHILD_OF: &str = "child_of"; +const REF_FOLLOWS_FROM: &str = "follows_from"; + +/// References provide a namespace for official OpenTracing reference types. +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] +pub enum References { + /// See http://opentracing.io/spec/#causal-span-references for + /// more information about ChildOf references. + ChildOf, + /// See http://opentracing.io/spec/#causal-span-references for + /// more information about FollowsFrom references. + FollowsFrom, +} + +impl References { + /// Returns the string representation for the enum reference variant. + pub fn as_str(&self) -> &'static str { + match *self { + References::ChildOf => REF_CHILD_OF, + References::FollowsFrom => REF_FOLLOWS_FROM, + } + } +} + +impl fmt::Display for References { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +impl FromStr for References { + type Err = ParseReferencesError; + + fn from_str(s: &str) -> Result { + match s { + REF_CHILD_OF => Ok(References::ChildOf), + REF_FOLLOWS_FROM => Ok(References::FollowsFrom), + _ => Err(ParseReferencesError::UnknownReference), + } + } +} + +/// Describes errors which can happen while parsing into the `References` enum. +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] +pub enum ParseReferencesError { + /// The provided reference is not known. + UnknownReference, +} + +impl Error for ParseReferencesError { + fn description(&self) -> &str { + match *self { + ParseReferencesError::UnknownReference => "Unknown Reference", + } + } + + fn cause(&self) -> Option<&Error> { + None + } +} + +impl fmt::Display for ParseReferencesError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.description()) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_reference_as_str() { + assert_eq!("child_of", References::ChildOf.as_str()); + assert_eq!("follows_from", References::FollowsFrom.as_str()); + } + + #[test] + fn test_reference_as_string() { + assert_eq!(String::from("child_of"), References::ChildOf.to_string()); + assert_eq!( + String::from("follows_from"), + References::FollowsFrom.to_string() + ); + } + + #[test] + fn test_reference_from_string() { + assert_eq!(Ok(References::ChildOf), References::from_str("child_of")); + assert_eq!(Ok(References::FollowsFrom), "follows_from".parse()); + assert_eq!( + Err(ParseReferencesError::UnknownReference), + References::from_str("some_other_field") + ); + } + +} diff --git a/opentracing-api/src/span.rs b/opentracing-api/src/span.rs new file mode 100644 index 0000000..487f892 --- /dev/null +++ b/opentracing-api/src/span.rs @@ -0,0 +1,83 @@ +use SpanContext; +use TagValue; + +/// The `Span` represents the OpenTracing specification's Span contract. +pub trait Span<'a> { + /// The associated `SpanContext`. + type Context: SpanContext<'a>; + + /// Retrieve the associated `SpanContext`. + /// + /// This may be called any time, including after `finish`. + fn context(&self) -> &Self::Context; + + /// Sets a key:value tag on the `Span`. + fn set_tag(&mut self, key: S, value: TagValue) + where + S: Into; + + /// Allows to unset a tag based on the given key. Noop if + /// it doesn't exist. + fn unset_tag(&mut self, key: S) + where + S: Into; + + /// Returns a tag value if set, none otherwise + fn tag(&self, key: S) -> Option<&TagValue> + where + S: Into; + + /// Record an event at the current walltime timestamp. + fn log(&mut self, event: String); + + /// Record an event at the given walltime timestamp. + fn log_at(&mut self, timestamp: u64, event: String); + + /// Sets a baggage item in the Span (and its SpanContext) as a key/value pair. + fn set_baggage_item(&mut self, key: S, value: String) + where + S: Into; + + /// Allows to unset a baggage item based on the given key. Noop if + /// it doesn't exist. + fn unset_baggage_item(&mut self, key: S) + where + S: Into; + + /// the value of the baggage item identified by the given key, or None if no such item + /// could be found. + fn baggage_item(&self, key: S) -> Option<&String> + where + S: Into; + + /// Sets the string name for the logical operation this span represents. + fn set_operation_name(&mut self, name: S) + where + S: Into; + + /// Returns the operation name if set, None otherwise. + fn operation_name(&self) -> &String; + + /// Sets the end timestamp to now and finishes (records) the span. + fn finish(self) -> FinishedSpan; + + /// Sets an explicit end timestamp and finishes (records) the span. + fn finish_at(self, timestamp: u64) -> FinishedSpan; +} + +pub struct FinishedSpan { + context: C, +} + +impl<'a, C> FinishedSpan +where + C: SpanContext<'a>, +{ + pub fn new(context: C) -> Self { + FinishedSpan { context } + } + + pub fn context(&self) -> &C { + &self.context + } +} diff --git a/opentracing-api/src/tag.rs b/opentracing-api/src/tag.rs index 9f3123d..0e7d458 100644 --- a/opentracing-api/src/tag.rs +++ b/opentracing-api/src/tag.rs @@ -171,6 +171,26 @@ impl fmt::Display for ParseTagsError { } } +/// Tags Values per spec can be Strings, Booleans or Numerics. +/// +/// Note that isize and usize are not included here since they +/// are only meant to be used as pointers-sized types only. +#[derive(Clone, PartialEq, PartialOrd, Debug)] +pub enum TagValue { + String(String), + Boolean(bool), + I8(i8), + I16(i16), + I32(i32), + I64(i64), + U8(u8), + U16(u16), + U32(u32), + U64(u64), + F32(f32), + F64(f64), +} + #[cfg(test)] mod tests { diff --git a/opentracing-mock/src/lib.rs b/opentracing-mock/src/lib.rs index d912641..cb94e4a 100644 --- a/opentracing-mock/src/lib.rs +++ b/opentracing-mock/src/lib.rs @@ -4,9 +4,9 @@ extern crate opentracing_api; -use opentracing_api::SpanContext; use std::collections::HashMap; use std::collections::hash_map::Iter as HashMapIter; +use opentracing_api::{FinishedSpan, Span, SpanContext, TagValue}; pub struct MockSpanContext { baggage: HashMap, @@ -14,9 +14,26 @@ pub struct MockSpanContext { impl MockSpanContext { /// Create a new `MockSpanContext` with the given baggage. - pub fn new(baggage: HashMap) -> Self { + fn new(baggage: HashMap) -> Self { MockSpanContext { baggage } } + + /// Create a new `MockSpanContext` with empty baggage. + fn empty() -> Self { + MockSpanContext::new(HashMap::new()) + } + + fn set_baggage_item(&mut self, key: String, value: String) { + self.baggage.insert(key, value); + } + + fn unset_baggage_item(&mut self, key: &String) { + self.baggage.remove(key); + } + + fn baggage_item(&self, key: &String) -> Option<&String> { + self.baggage.get(key) + } } impl<'a> SpanContext<'a> for MockSpanContext { @@ -27,11 +44,107 @@ impl<'a> SpanContext<'a> for MockSpanContext { } } +pub struct MockSpan { + ctx: MockSpanContext, + tags: HashMap, + op_name: String, +} + +impl MockSpan { + pub fn new(op_name: S) -> Self + where + S: Into, + { + Self { + ctx: MockSpanContext::empty(), + tags: HashMap::new(), + op_name: op_name.into(), + } + } +} + +impl<'a> Span<'a> for MockSpan { + type Context = MockSpanContext; + + fn context(&self) -> &Self::Context { + &self.ctx + } + + fn set_tag(&mut self, key: S, value: TagValue) + where + S: Into, + { + self.tags.insert(key.into(), value); + } + + fn unset_tag(&mut self, key: S) + where + S: Into, + { + self.tags.remove(&key.into()); + } + + fn tag(&self, key: S) -> Option<&TagValue> + where + S: Into, + { + self.tags.get(&key.into()) + } + + fn log(&mut self, _event: String) { + unimplemented!() + } + + fn log_at(&mut self, _timestamp: u64, _event: String) { + unimplemented!() + } + + fn set_baggage_item(&mut self, key: S, value: String) + where + S: Into, + { + self.ctx.set_baggage_item(key.into(), value); + } + + fn unset_baggage_item(&mut self, key: S) + where + S: Into, + { + self.ctx.unset_baggage_item(&key.into()) + } + + fn baggage_item(&self, key: S) -> Option<&String> + where + S: Into, + { + self.ctx.baggage_item(&key.into()) + } + + fn set_operation_name(&mut self, name: S) + where + S: Into, + { + self.op_name = name.into(); + } + + fn operation_name(&self) -> &String { + &self.op_name + } + + fn finish(self) -> FinishedSpan { + unimplemented!() + } + + fn finish_at(self, _timestamp: u64) -> FinishedSpan { + unimplemented!() + } +} + #[cfg(test)] mod tests { - use super::MockSpanContext; - use opentracing_api::SpanContext; + use super::{MockSpan, MockSpanContext}; + use opentracing_api::{Span, SpanContext, TagValue}; use std::collections::HashMap; #[test] @@ -48,4 +161,34 @@ mod tests { assert_eq!(None, iter.next()); } + #[test] + fn test_set_get_unset_tag() { + let mut span = MockSpan::new("myop"); + assert_eq!(None, span.tag("key")); + span.set_tag("key", TagValue::String("some content".into())); + assert_eq!( + Some(&TagValue::String("some content".into())), + span.tag("key") + ); + span.unset_tag("key"); + assert_eq!(None, span.tag("key")); + } + + #[test] + fn test_set_get_baggage() { + let mut span = MockSpan::new("myop"); + assert_eq!(None, span.baggage_item("key")); + span.set_baggage_item("key", "value".into()); + assert_eq!(Some(&String::from("value")), span.baggage_item("key")); + span.unset_baggage_item("key"); + assert_eq!(None, span.baggage_item("key")); + } + + #[test] + fn test_set_get_operation_name() { + let mut span = MockSpan::new("my_op_name"); + assert_eq!(&String::from("my_op_name"), span.operation_name()); + span.set_operation_name("a_different_op_name"); + assert_eq!(&String::from("a_different_op_name"), span.operation_name()); + } } diff --git a/opentracing-noop/src/lib.rs b/opentracing-noop/src/lib.rs index abf365c..1494e8d 100644 --- a/opentracing-noop/src/lib.rs +++ b/opentracing-noop/src/lib.rs @@ -4,7 +4,7 @@ extern crate opentracing_api; -use opentracing_api::SpanContext; +use opentracing_api::{FinishedSpan, Span, SpanContext, TagValue}; use std::iter::{empty, Empty}; /// The `NoopSpanContext` just returns an empty iterator on @@ -25,6 +25,88 @@ impl<'a> SpanContext<'a> for NoopSpanContext { } } +pub struct NoopSpan { + ctx: NoopSpanContext, + op_name: String, +} + +impl NoopSpan { + pub fn new() -> Self { + Self { + ctx: NoopSpanContext::default(), + op_name: "NoopSpan".into(), + } + } +} + +impl<'a> Span<'a> for NoopSpan { + type Context = NoopSpanContext; + + fn context(&self) -> &Self::Context { + &self.ctx + } + + fn set_tag(&mut self, _key: S, _value: TagValue) + where + S: Into, + { + } + + fn unset_tag(&mut self, _key: S) + where + S: Into, + { + } + + fn tag(&self, _key: S) -> Option<&TagValue> + where + S: Into, + { + None + } + + fn log(&mut self, _event: String) {} + + fn log_at(&mut self, _timestamp: u64, _event: String) {} + + fn set_baggage_item(&mut self, _key: S, _value: String) + where + S: Into, + { + } + + fn unset_baggage_item(&mut self, _key: S) + where + S: Into, + { + } + + fn baggage_item(&self, _key: S) -> Option<&String> + where + S: Into, + { + None + } + + fn set_operation_name(&mut self, _name: S) + where + S: Into, + { + } + + fn operation_name(&self) -> &String { + &self.op_name + } + + fn finish(self) -> FinishedSpan { + self.finish_at(0) + } + + fn finish_at(self, _timestamp: u64) -> FinishedSpan { + FinishedSpan::new(self.ctx) + } +} + #[cfg(test)] mod tests {