From edc52181cfbd347e1e7085988c0d170bbe0d9a49 Mon Sep 17 00:00:00 2001 From: Dom Dwyer Date: Tue, 12 Apr 2022 15:30:44 +0100 Subject: [PATCH 1/2] feat: support static tracing tags Adds support for annotating all Jaeger tracing spans with a set of static process tags. --- trace_exporters/src/jaeger.rs | 123 ++++++++++++++++++++++++++++++++-- 1 file changed, 119 insertions(+), 4 deletions(-) diff --git a/trace_exporters/src/jaeger.rs b/trace_exporters/src/jaeger.rs index da05196d67..c929e6cdac 100644 --- a/trace_exporters/src/jaeger.rs +++ b/trace_exporters/src/jaeger.rs @@ -1,8 +1,11 @@ -use std::net::{SocketAddr, ToSocketAddrs, UdpSocket}; +use std::{ + net::{SocketAddr, ToSocketAddrs, UdpSocket}, + str::FromStr, +}; use async_trait::async_trait; -use observability_deps::tracing::{error, info}; +use observability_deps::tracing::*; use trace::span::Span; use crate::export::AsyncExport; @@ -12,6 +15,49 @@ use thrift::protocol::{TCompactInputProtocol, TCompactOutputProtocol}; mod span; +/// A key=value pair for span annotations. +#[derive(Debug, Clone)] +pub struct JaegerTag { + key: String, + value: String, +} + +impl JaegerTag { + /// Create a new static tag for all jaeger spans. + pub fn new(key: impl Into, value: impl Into) -> Self { + Self { + key: key.into(), + value: value.into(), + } + } +} + +impl From for jaeger::Tag { + fn from(t: JaegerTag) -> Self { + Self::new( + t.key, + jaeger::TagType::String, + Some(t.value), + None, + None, + None, + None, + ) + } +} + +impl FromStr for JaegerTag { + type Err = Box; + + fn from_str(s: &str) -> Result { + let parts = s.split('=').collect::>(); + match *parts { + [key, value] if !key.is_empty() && !value.is_empty() => Ok(Self::new(key, value)), + _ => Err(format!("invalid key=value pair ({})", s).into()), + } + } +} + /// `JaegerAgentExporter` receives span data and writes it over UDP to a local jaeger agent /// /// Note: will drop data if the UDP socket would block @@ -26,6 +72,9 @@ pub struct JaegerAgentExporter { /// Spans should be assigned a sequential sequence number /// to allow jaeger to better detect dropped spans next_sequence: i64, + + /// Optional static tags to annotate every span with. + tags: Option>, } impl JaegerAgentExporter { @@ -61,16 +110,25 @@ impl JaegerAgentExporter { service_name, client, next_sequence: 0, + tags: None, }) } + /// Annotate all spans emitted by this exporter with the specified static + /// tags. + pub fn with_tags(self, tags: &[JaegerTag]) -> Self { + debug!(?tags, "setting static jaeger span tags"); + let tags = Some(tags.iter().cloned().map(Into::into).collect()); + Self { tags, ..self } + } + fn make_batch(&mut self, spans: Vec) -> jaeger::Batch { let seq_no = Some(self.next_sequence); self.next_sequence += 1; jaeger::Batch { process: jaeger::Process { service_name: self.service_name.clone(), - tags: None, + tags: self.tags.clone(), }, spans: spans.into_iter().map(Into::into).collect(), seq_no, @@ -214,6 +272,34 @@ mod tests { } } + #[test] + fn test_jaeger_tag_from_str() { + "".parse::().expect_err("empty tag should fail"); + "key" + .parse::() + .expect_err("no value should fail"); + "key=" + .parse::() + .expect_err("no value should fail"); + "key==" + .parse::() + .expect_err("no value should fail"); + "=value" + .parse::() + .expect_err("no key should fail"); + "==value" + .parse::() + .expect_err("empty key should fail"); + "key==value" + .parse::() + .expect_err("too many = should fail"); + "=".parse::() + .expect_err("empty key value should fail"); + "key=value" + .parse::() + .expect("valid form should succeed"); + } + #[tokio::test] async fn test_jaeger() { let server = UdpSocket::bind("0.0.0.0:0").unwrap(); @@ -221,8 +307,22 @@ mod tests { .set_read_timeout(Some(std::time::Duration::from_secs(1))) .unwrap(); + let tags = [JaegerTag::new("bananas", "great")]; let address = server.local_addr().unwrap(); - let mut exporter = JaegerAgentExporter::new("service_name".to_string(), address).unwrap(); + let mut exporter = JaegerAgentExporter::new("service_name".to_string(), address) + .unwrap() + .with_tags(&tags); + + // Encoded form of tags. + let want_tags = [jaeger::Tag { + key: "bananas".into(), + v_str: Some("great".into()), + v_type: jaeger::TagType::String, + v_double: None, + v_bool: None, + v_long: None, + v_binary: None, + }]; let batches = Arc::new(Mutex::new(vec![])); @@ -271,11 +371,26 @@ mod tests { assert_eq!(b1.spans.len(), 2); assert_eq!(b1.process.service_name.as_str(), "service_name"); assert_eq!(b1.seq_no.unwrap(), 0); + let got_tags = b1 + .process + .tags + .as_ref() + .expect("expected static process tags"); + assert_eq!(got_tags, &want_tags); let b2 = &batches[1]; assert_eq!(b2.spans.len(), 1); assert_eq!(b2.process.service_name.as_str(), "service_name"); assert_eq!(b2.seq_no.unwrap(), 1); + let got_tags = b2 + .process + .tags + .as_ref() + .expect("expected static process tags"); + assert_eq!(got_tags, &want_tags); + + // Span tags should be constant + assert_eq!(b1.process.tags, b2.process.tags); let b1_s0 = &b1.spans[0]; From aa80776d0bb82c2a6b1657b61c0c93f5141f5271 Mon Sep 17 00:00:00 2001 From: Dom Dwyer Date: Tue, 12 Apr 2022 15:31:28 +0100 Subject: [PATCH 2/2] feat: enable static Jaeger span tags Exposes the static process tags via a CLI flag ("--traces-jaeger-tags") or env var ("TRACES_EXPORTER_JAEGER_TAGS") that accepts a key=value, comma-delimited tag string. --- trace_exporters/src/lib.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/trace_exporters/src/lib.rs b/trace_exporters/src/lib.rs index 75afb4e73c..456adefc8c 100644 --- a/trace_exporters/src/lib.rs +++ b/trace_exporters/src/lib.rs @@ -9,6 +9,7 @@ use crate::export::AsyncExporter; use crate::jaeger::JaegerAgentExporter; +use jaeger::JaegerTag; use snafu::Snafu; use std::num::NonZeroU16; use std::sync::Arc; @@ -103,6 +104,19 @@ pub struct TracingConfig { default_value = "jaeger-debug-id" )] pub traces_jaeger_debug_name: String, + + /// Tracing: set of key=value pairs to annotate tracing spans with. + /// + /// Use a comma-delimited string to set multiple pairs: env=prod,region=eu-1 + /// + /// Only used if `--traces-exporter` is "jaeger". + #[clap( + long = "--traces-jaeger-tags", + env = "TRACES_EXPORTER_JAEGER_TAGS", + value_delimiter = ',', + parse(try_from_str) + )] + pub traces_jaeger_tags: Option>, } impl TracingConfig { @@ -154,7 +168,12 @@ fn jaeger_exporter(config: &TracingConfig) -> Result> { ); let service_name = &config.traces_exporter_jaeger_service_name; - let jaeger = JaegerAgentExporter::new(service_name.clone(), agent_endpoint)?; + let mut jaeger = JaegerAgentExporter::new(service_name.clone(), agent_endpoint)?; + + // Use any specified static span tags. + if let Some(tags) = &config.traces_jaeger_tags { + jaeger = jaeger.with_tags(tags); + } Ok(Arc::new(AsyncExporter::new(jaeger))) }