Skip to content

Commit 32e5421

Browse files
authored
feat(stackdriver): Added mappings from OTel attributes to Google Cloud Traces (open-telemetry#744)
1 parent 5696253 commit 32e5421

File tree

2 files changed

+213
-13
lines changed

2 files changed

+213
-13
lines changed

opentelemetry-stackdriver/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ http = "0.2"
1717
hyper = "0.14.2"
1818
hyper-rustls = { version = "0.22.1", optional = true }
1919
opentelemetry = { version = "0.17", path = "../opentelemetry" }
20+
opentelemetry-semantic-conventions = { version = "0.9", path = "../opentelemetry-semantic-conventions" }
2021
prost = "0.9"
2122
prost-types = "0.9"
2223
thiserror = "1.0.30"

opentelemetry-stackdriver/src/lib.rs

Lines changed: 212 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,17 @@ use async_trait::async_trait;
2424
use futures::stream::StreamExt;
2525
use opentelemetry::{
2626
global::handle_error,
27-
sdk::export::{
28-
trace::{ExportResult, SpanData, SpanExporter},
29-
ExportError,
27+
sdk::{
28+
export::{
29+
trace::{ExportResult, SpanData, SpanExporter},
30+
ExportError,
31+
},
32+
trace::EvictedHashMap,
3033
},
3134
trace::TraceError,
3235
Value,
3336
};
37+
use opentelemetry_semantic_conventions as semcov;
3438
use thiserror::Error;
3539
#[cfg(any(feature = "yup-authorizer", feature = "gcp_auth"))]
3640
use tonic::metadata::MetadataValue;
@@ -240,12 +244,6 @@ where
240244
let mut entries = Vec::new();
241245
let mut spans = Vec::with_capacity(batch.len());
242246
for span in batch {
243-
let attribute_map = span
244-
.attributes
245-
.into_iter()
246-
.map(|(key, value)| (key.as_str().to_owned(), value.into()))
247-
.collect();
248-
249247
let trace_id = hex::encode(span.span_context.trace_id().to_bytes());
250248
let span_id = hex::encode(span.span_context.span_id().to_bytes());
251249
let time_event = match &self.log_client {
@@ -321,10 +319,7 @@ where
321319
parent_span_id: hex::encode(span.parent_span_id.to_bytes()),
322320
start_time: Some(span.start_time.into()),
323321
end_time: Some(span.end_time.into()),
324-
attributes: Some(Attributes {
325-
attribute_map,
326-
..Default::default()
327-
}),
322+
attributes: Some(span.attributes.into()),
328323
time_events: Some(TimeEvents {
329324
time_event,
330325
..Default::default()
@@ -657,3 +652,207 @@ pub enum MonitoredResource {
657652

658653
const TRACE_APPEND: &str = "https://www.googleapis.com/auth/trace.append";
659654
const LOGGING_WRITE: &str = "https://www.googleapis.com/auth/logging.write";
655+
const HTTP_PATH_ATTRIBUTE: &str = "http.path";
656+
657+
const GCP_HTTP_HOST: &str = "/http/host";
658+
const GCP_HTTP_METHOD: &str = "/http/method";
659+
const GCP_HTTP_TARGET: &str = "/http/path";
660+
const GCP_HTTP_URL: &str = "/http/url";
661+
const GCP_HTTP_USER_AGENT: &str = "/http/user_agent";
662+
const GCP_HTTP_STATUS_CODE: &str = "/http/status_code";
663+
const GCP_HTTP_ROUTE: &str = "/http/route";
664+
const GCP_HTTP_PATH: &str = "/http/path";
665+
const GCP_SERVICE_NAME: &str = "g.co/gae/app/module";
666+
667+
impl From<EvictedHashMap> for Attributes {
668+
fn from(attributes: EvictedHashMap) -> Self {
669+
let mut dropped_attributes_count: i32 = 0;
670+
let attribute_map = attributes
671+
.into_iter()
672+
.flat_map(|(k, v)| {
673+
let key = k.as_str();
674+
if key.len() > 128 {
675+
dropped_attributes_count += 1;
676+
return None;
677+
}
678+
679+
if semcov::trace::HTTP_HOST == k {
680+
return Some((GCP_HTTP_HOST.to_owned(), v.into()));
681+
}
682+
683+
if semcov::trace::HTTP_METHOD == k {
684+
return Some((GCP_HTTP_METHOD.to_owned(), v.into()));
685+
}
686+
687+
if semcov::trace::HTTP_TARGET == k {
688+
return Some((GCP_HTTP_TARGET.to_owned(), v.into()));
689+
}
690+
691+
if semcov::trace::HTTP_URL == k {
692+
return Some((GCP_HTTP_URL.to_owned(), v.into()));
693+
}
694+
695+
if semcov::trace::HTTP_USER_AGENT == k {
696+
return Some((GCP_HTTP_USER_AGENT.to_owned(), v.into()));
697+
}
698+
699+
if semcov::trace::HTTP_STATUS_CODE == k {
700+
return Some((GCP_HTTP_STATUS_CODE.to_owned(), v.into()));
701+
}
702+
703+
if semcov::trace::HTTP_ROUTE == k {
704+
return Some((GCP_HTTP_ROUTE.to_owned(), v.into()));
705+
};
706+
707+
if semcov::resource::SERVICE_NAME == k {
708+
return Some((GCP_SERVICE_NAME.to_owned(), v.into()));
709+
};
710+
711+
if HTTP_PATH_ATTRIBUTE == key {
712+
return Some((GCP_HTTP_PATH.to_owned(), v.into()));
713+
}
714+
715+
Some((key.to_owned(), v.into()))
716+
})
717+
.collect();
718+
Attributes {
719+
attribute_map,
720+
dropped_attributes_count,
721+
}
722+
}
723+
}
724+
725+
#[cfg(test)]
726+
mod tests {
727+
use super::*;
728+
729+
use opentelemetry::{sdk::trace::EvictedHashMap, KeyValue, Value};
730+
use opentelemetry_semantic_conventions as semcov;
731+
732+
#[test]
733+
fn test_attributes_mapping() {
734+
let capacity = 10;
735+
let mut attributes = EvictedHashMap::new(capacity, 0);
736+
737+
// hostAttribute = "http.host"
738+
attributes.insert(semcov::trace::HTTP_HOST.string("example.com:8080"));
739+
740+
// methodAttribute = "http.method"
741+
attributes.insert(semcov::trace::HTTP_METHOD.string("POST"));
742+
743+
// pathAttribute = "http.path"
744+
attributes.insert(KeyValue::new(
745+
"http.path",
746+
Value::String("/path/12314/?q=ddds#123".into()),
747+
));
748+
749+
// urlAttribute = "http.url"
750+
attributes.insert(
751+
semcov::trace::HTTP_URL.string("https://example.com:8080/webshop/articles/4?s=1"),
752+
);
753+
754+
// userAgentAttribute = "http.user_agent"
755+
attributes
756+
.insert(semcov::trace::HTTP_USER_AGENT.string("CERN-LineMode/2.15 libwww/2.17b3"));
757+
758+
// statusCodeAttribute = "http.status_code"
759+
attributes.insert(semcov::trace::HTTP_STATUS_CODE.i64(200));
760+
761+
// statusCodeAttribute = "http.route"
762+
attributes.insert(semcov::trace::HTTP_ROUTE.string("/webshop/articles/:article_id"));
763+
764+
// serviceAttribute = "service.name"
765+
attributes.insert(semcov::resource::SERVICE_NAME.string("Test Service Name"));
766+
767+
let actual: Attributes = attributes.into();
768+
769+
assert_eq!(actual.attribute_map.len(), 8);
770+
assert_eq!(actual.dropped_attributes_count, 0);
771+
assert_eq!(
772+
actual.attribute_map.get("/http/host"),
773+
Some(&AttributeValue::from(Value::String(
774+
"example.com:8080".into()
775+
)))
776+
);
777+
assert_eq!(
778+
actual.attribute_map.get("/http/method"),
779+
Some(&AttributeValue::from(Value::String("POST".into()))),
780+
);
781+
assert_eq!(
782+
actual.attribute_map.get("/http/path"),
783+
Some(&AttributeValue::from(Value::String(
784+
"/path/12314/?q=ddds#123".into()
785+
))),
786+
);
787+
assert_eq!(
788+
actual.attribute_map.get("/http/route"),
789+
Some(&AttributeValue::from(Value::String(
790+
"/webshop/articles/:article_id".into()
791+
))),
792+
);
793+
assert_eq!(
794+
actual.attribute_map.get("/http/url"),
795+
Some(&AttributeValue::from(Value::String(
796+
"https://example.com:8080/webshop/articles/4?s=1".into(),
797+
))),
798+
);
799+
assert_eq!(
800+
actual.attribute_map.get("/http/user_agent"),
801+
Some(&AttributeValue::from(Value::String(
802+
"CERN-LineMode/2.15 libwww/2.17b3".into()
803+
))),
804+
);
805+
assert_eq!(
806+
actual.attribute_map.get("/http/status_code"),
807+
Some(&AttributeValue::from(Value::I64(200))),
808+
);
809+
assert_eq!(
810+
actual.attribute_map.get("g.co/gae/app/module"),
811+
Some(&AttributeValue::from(Value::String(
812+
"Test Service Name".into()
813+
))),
814+
);
815+
}
816+
817+
#[test]
818+
fn test_attributes_mapping_http_target() {
819+
let capacity = 10;
820+
let mut attributes = EvictedHashMap::new(capacity, 0);
821+
822+
// hostAttribute = "http.target"
823+
attributes.insert(semcov::trace::HTTP_TARGET.string("/path/12314/?q=ddds#123"));
824+
825+
let actual: Attributes = attributes.into();
826+
827+
assert_eq!(actual.attribute_map.len(), 1);
828+
assert_eq!(actual.dropped_attributes_count, 0);
829+
assert_eq!(
830+
actual.attribute_map.get("/http/path"),
831+
Some(&AttributeValue::from(Value::String(
832+
"/path/12314/?q=ddds#123".into()
833+
))),
834+
);
835+
}
836+
837+
#[test]
838+
fn test_attributes_mapping_dropped_attributes_count() {
839+
let capacity = 10;
840+
let mut attributes = EvictedHashMap::new(capacity, 0);
841+
attributes.insert(KeyValue::new("answer", Value::I64(42)));
842+
attributes.insert(KeyValue::new("long_attribute_key_dvwmacxpeefbuemoxljmqvldjxmvvihoeqnuqdsyovwgljtnemouidabhkmvsnauwfnaihekcfwhugejboiyfthyhmkpsaxtidlsbwsmirebax", Value::String("Some value".into())));
843+
844+
let actual: Attributes = attributes.into();
845+
assert_eq!(
846+
actual,
847+
Attributes {
848+
attribute_map: HashMap::from([(
849+
"answer".into(),
850+
AttributeValue::from(Value::I64(42))
851+
),]),
852+
dropped_attributes_count: 1,
853+
}
854+
);
855+
assert_eq!(actual.attribute_map.len(), 1);
856+
assert_eq!(actual.dropped_attributes_count, 1);
857+
}
858+
}

0 commit comments

Comments
 (0)