Skip to content

Commit 232a87e

Browse files
authored
Set status tag in Zipkin & Jaeger (#383)
1 parent f780d91 commit 232a87e

File tree

5 files changed

+243
-28
lines changed

5 files changed

+243
-28
lines changed

opentelemetry-jaeger/src/exporter/mod.rs

Lines changed: 108 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -623,11 +623,6 @@ fn build_span_tags(
623623
}
624624
}
625625

626-
// Ensure error status is set
627-
if status_code == StatusCode::Error && !user_overrides.error {
628-
tags.push(Key::new(ERROR).bool(true).into())
629-
}
630-
631626
if !user_overrides.span_kind {
632627
tags.push(Key::new(SPAN_KIND).string(kind.to_string()).into());
633628
}
@@ -636,8 +631,31 @@ fn build_span_tags(
636631
tags.push(KeyValue::new(STATUS_CODE, status_code as i64).into());
637632
}
638633

639-
if !user_overrides.status_message {
640-
tags.push(Key::new(STATUS_MESSAGE).string(status_message).into());
634+
if status_code != StatusCode::Unset {
635+
// Ensure error status is set
636+
if status_code == StatusCode::Error {
637+
tags.push(Key::new(ERROR).bool(true).into());
638+
}
639+
tags.push(
640+
Key::new(OTEL_STATUS_CODE)
641+
.string::<&'static str>(status_code.as_str())
642+
.into(),
643+
);
644+
// set status message if there is one
645+
if !status_message.is_empty() {
646+
if !user_overrides.status_message {
647+
tags.push(
648+
Key::new(STATUS_MESSAGE)
649+
.string(status_message.clone())
650+
.into(),
651+
);
652+
}
653+
tags.push(
654+
Key::new(OTEL_STATUS_DESCRIPTION)
655+
.string(status_message)
656+
.into(),
657+
);
658+
}
641659
}
642660

643661
Some(tags)
@@ -647,6 +665,8 @@ const ERROR: &str = "error";
647665
const SPAN_KIND: &str = "span.kind";
648666
const STATUS_CODE: &str = "status.code";
649667
const STATUS_MESSAGE: &str = "status.message";
668+
const OTEL_STATUS_CODE: &str = "otel.status_code";
669+
const OTEL_STATUS_DESCRIPTION: &str = "otel.status_description";
650670

651671
#[derive(Default)]
652672
struct UserOverrides {
@@ -708,7 +728,7 @@ impl ExportError for Error {
708728

709729
#[cfg(test)]
710730
#[cfg(feature = "collector_client")]
711-
mod tests {
731+
mod collector_client_tests {
712732
use crate::exporter::thrift::jaeger::Batch;
713733
use crate::new_pipeline;
714734
use opentelemetry::trace::TraceError;
@@ -755,3 +775,83 @@ mod tests {
755775
Ok(())
756776
}
757777
}
778+
779+
#[cfg(test)]
780+
mod tests {
781+
use crate::exporter::thrift::jaeger::Tag;
782+
use crate::exporter::{build_span_tags, OTEL_STATUS_CODE, OTEL_STATUS_DESCRIPTION};
783+
use opentelemetry::sdk::trace::EvictedHashMap;
784+
use opentelemetry::trace::{SpanKind, StatusCode};
785+
786+
fn assert_tag_contains(tags: Vec<Tag>, key: &'static str, expect_val: &'static str) {
787+
assert_eq!(
788+
tags.into_iter()
789+
.filter(|tag| tag.key.as_str() == key
790+
&& tag.v_str.as_deref().unwrap_or("") == expect_val)
791+
.count(),
792+
1,
793+
"Expect a tag {} with value {} but found nothing",
794+
key,
795+
expect_val
796+
);
797+
}
798+
799+
fn assert_tag_not_contains(tags: Vec<Tag>, key: &'static str) {
800+
assert_eq!(
801+
tags.into_iter()
802+
.filter(|tag| tag.key.as_str() == key)
803+
.count(),
804+
0,
805+
"Not expect tag {}, but found something",
806+
key
807+
);
808+
}
809+
810+
fn get_error_tag_test_data() -> Vec<(
811+
StatusCode,
812+
String,
813+
Option<&'static str>,
814+
Option<&'static str>,
815+
)> {
816+
// StatusCode, error message, OTEL_STATUS_CODE tag value, OTEL_STATUS_DESCRIPTION tag value
817+
vec![
818+
(StatusCode::Error, "".into(), Some("ERROR"), None),
819+
(StatusCode::Unset, "".into(), None, None),
820+
// When status is ok, no description should be in span data. This should be ensured by Otel API
821+
(StatusCode::Ok, "".into(), Some("OK"), None),
822+
(
823+
StatusCode::Error,
824+
"have message".into(),
825+
Some("ERROR"),
826+
Some("have message"),
827+
),
828+
(StatusCode::Unset, "have message".into(), None, None),
829+
]
830+
}
831+
832+
#[test]
833+
fn test_set_status() -> Result<(), Box<dyn std::error::Error>> {
834+
for (status_code, error_msg, status_tag_val, msg_tag_val) in get_error_tag_test_data() {
835+
let tags = build_span_tags(
836+
EvictedHashMap::new(20, 20),
837+
None,
838+
status_code,
839+
error_msg,
840+
SpanKind::Client,
841+
)
842+
.unwrap_or_default();
843+
if let Some(val) = status_tag_val {
844+
assert_tag_contains(tags.clone(), OTEL_STATUS_CODE, val);
845+
} else {
846+
assert_tag_not_contains(tags.clone(), OTEL_STATUS_CODE);
847+
}
848+
849+
if let Some(val) = msg_tag_val {
850+
assert_tag_contains(tags.clone(), OTEL_STATUS_DESCRIPTION, val);
851+
} else {
852+
assert_tag_not_contains(tags.clone(), OTEL_STATUS_DESCRIPTION);
853+
}
854+
}
855+
Ok(())
856+
}
857+
}

opentelemetry-zipkin/src/exporter/model/mod.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@ pub(crate) mod span;
1212

1313
use endpoint::Endpoint;
1414

15-
/// Instrument Library name MUST be reported in Jaeger Span tags with the following key
1615
const INSTRUMENTATION_LIBRARY_NAME: &str = "otel.library.name";
17-
18-
/// Instrument Library version MUST be reported in Jaeger Span tags with the following key
1916
const INSTRUMENTATION_LIBRARY_VERSION: &str = "otel.library.version";
17+
const OTEL_ERROR_DESCRIPTION: &str = "error";
18+
const OTEL_STATUS_CODE: &str = "otel.status_code";
2019

2120
/// Converts `Event` into an `annotation::Annotation`
2221
impl Into<annotation::Annotation> for Event {
@@ -58,6 +57,7 @@ fn into_zipkin_span_kind(kind: SpanKind) -> Option<span::Kind> {
5857
/// Converts a `trace::SpanData` to a `span::SpanData` for a given `ExporterConfig`, which can then
5958
/// be ingested into a Zipkin collector.
6059
pub(crate) fn into_zipkin_span(local_endpoint: Endpoint, span_data: trace::SpanData) -> span::Span {
60+
// see tests in create/exporter/model/span.rs
6161
let mut user_defined_span_kind = false;
6262
let mut tags = map_from_kvs(
6363
span_data
@@ -88,15 +88,16 @@ pub(crate) fn into_zipkin_span(local_endpoint: Endpoint, span_data: trace::SpanD
8888
]
8989
.iter()
9090
.filter_map(|(key, val)| val.map(|val| KeyValue::new(*key, val))),
91-
),
91+
)
92+
.filter(|kv| kv.key.as_str() != "error"),
9293
);
93-
9494
if let Some(status_code) = from_statuscode_to_str(span_data.status_code) {
95-
tags.insert("otel.status_code".into(), status_code.into());
95+
if status_code == "ERROR" {
96+
tags.insert(OTEL_ERROR_DESCRIPTION.into(), span_data.status_message);
97+
}
98+
tags.insert(OTEL_STATUS_CODE.into(), status_code.into());
9699
}
97100

98-
tags.insert("otel.status_description".into(), span_data.status_message);
99-
100101
span::Span::builder()
101102
.trace_id(span_data.span_context.trace_id().to_hex())
102103
.parent_id(span_data.parent_span_id.to_hex())

opentelemetry-zipkin/src/exporter/model/span.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,14 @@ mod tests {
5858
use crate::exporter::model::annotation::Annotation;
5959
use crate::exporter::model::endpoint::Endpoint;
6060
use crate::exporter::model::span::{Kind, Span};
61+
use crate::exporter::model::{into_zipkin_span, OTEL_ERROR_DESCRIPTION, OTEL_STATUS_CODE};
62+
use opentelemetry::sdk::export::trace::SpanData;
63+
use opentelemetry::sdk::trace::{EvictedHashMap, EvictedQueue};
64+
use opentelemetry::trace::{SpanContext, SpanId, SpanKind, StatusCode, TraceId};
6165
use std::collections::HashMap;
6266
use std::net::Ipv4Addr;
67+
use std::sync::Arc;
68+
use std::time::SystemTime;
6369

6470
#[test]
6571
fn test_empty() {
@@ -112,4 +118,77 @@ mod tests {
112118
let result = serde_json::to_string(&span).unwrap();
113119
assert_eq!(result, desired.to_owned());
114120
}
121+
122+
fn assert_tag_contains(
123+
tags: &HashMap<String, String>,
124+
key: &'static str,
125+
expected_val: Option<&'static str>,
126+
) {
127+
let val = tags.get::<String>(&key.to_string()).map(|s| s.as_str());
128+
assert_eq!(
129+
val,
130+
expected_val,
131+
"expect value of key {} to be {}, but got {}",
132+
key,
133+
expected_val.unwrap_or("none"),
134+
val.unwrap_or("none")
135+
);
136+
}
137+
138+
fn get_set_status_test_data() -> Vec<(
139+
StatusCode,
140+
String,
141+
Option<&'static str>,
142+
Option<&'static str>,
143+
)> {
144+
// status code, status message, whether OTEL_STATUS_CODE is set, whether OTEL_ERROR_DESCRIPTION is set, whether error tag is set
145+
vec![
146+
(StatusCode::Ok, "".into(), Some("OK"), None),
147+
(StatusCode::Error, "".into(), Some("ERROR"), Some("")),
148+
(
149+
StatusCode::Error,
150+
"error msg".into(),
151+
Some("ERROR"),
152+
Some("error msg"),
153+
),
154+
(StatusCode::Unset, "whatever".into(), None, None),
155+
]
156+
}
157+
158+
#[test]
159+
fn test_set_status() -> Result<(), Box<dyn std::error::Error>> {
160+
for (status_code, status_msg, status_tag_val, status_msg_tag_val) in
161+
get_set_status_test_data()
162+
{
163+
let span_data = SpanData {
164+
span_context: SpanContext::new(
165+
TraceId::from_u128(1),
166+
SpanId::from_u64(1),
167+
0,
168+
false,
169+
Default::default(),
170+
),
171+
parent_span_id: SpanId::from_u64(1),
172+
span_kind: SpanKind::Client,
173+
name: "".to_string(),
174+
start_time: SystemTime::now(),
175+
end_time: SystemTime::now(),
176+
attributes: EvictedHashMap::new(20, 20),
177+
message_events: EvictedQueue::new(20),
178+
links: EvictedQueue::new(20),
179+
status_code,
180+
status_message: status_msg,
181+
resource: Arc::new(Default::default()),
182+
instrumentation_lib: Default::default(),
183+
};
184+
let local_endpoint = Endpoint::new("test".into(), None);
185+
let span = into_zipkin_span(local_endpoint, span_data);
186+
if let Some(tags) = span.tags.as_ref() {
187+
assert_tag_contains(tags, OTEL_STATUS_CODE, status_tag_val);
188+
assert_tag_contains(tags, OTEL_ERROR_DESCRIPTION, status_msg_tag_val);
189+
};
190+
}
191+
192+
Ok(())
193+
}
115194
}

opentelemetry/src/api/trace/span.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ pub trait Span: fmt::Debug + 'static + Send + Sync {
123123
fn set_attribute(&self, attribute: KeyValue);
124124

125125
/// Sets the status of the `Span`. If used, this will override the default `Span`
126-
/// status, which is `OK`.
126+
/// status, which is `Unset`. `message` MUST be ignored when the status is `OK` or `Unset`
127127
///
128128
/// Only the value of the last call will be recorded, and implementations are free
129129
/// to ignore previous calls.
@@ -241,7 +241,7 @@ impl fmt::Display for SpanKind {
241241
/// It's composed of a canonical code in conjunction with an optional
242242
/// descriptive message.
243243
#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
244-
#[derive(Clone, Debug, PartialEq)]
244+
#[derive(Clone, Debug, PartialEq, Copy)]
245245
pub enum StatusCode {
246246
/// The default status.
247247
Unset = 0,
@@ -250,3 +250,14 @@ pub enum StatusCode {
250250
/// The operation contains an error.
251251
Error = 2,
252252
}
253+
254+
impl StatusCode {
255+
/// Return a static str that represent the status code
256+
pub fn as_str(&self) -> &'static str {
257+
match self {
258+
StatusCode::Unset => "",
259+
StatusCode::Ok => "OK",
260+
StatusCode::Error => "ERROR",
261+
}
262+
}
263+
}

opentelemetry/src/sdk/trace/span.rs

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,13 @@ impl crate::trace::Span for Span {
129129
}
130130

131131
/// Sets the status of the `Span`. If used, this will override the default `Span`
132-
/// status, which is `Unset`.
132+
/// status, which is `Unset`. `message` MUST be ignored when the status is `OK` or `Unset`
133133
fn set_status(&self, code: StatusCode, message: String) {
134134
self.with_data(|data| {
135+
if code == StatusCode::Error {
136+
data.status_message = message;
137+
}
135138
data.status_code = code;
136-
data.status_message = message
137139
});
138140
}
139141

@@ -353,14 +355,36 @@ mod tests {
353355

354356
#[test]
355357
fn set_status() {
356-
let span = create_span();
357-
let status = StatusCode::Ok;
358-
let message = "OK".to_string();
359-
span.set_status(status.clone(), message.clone());
360-
span.with_data(|data| {
361-
assert_eq!(data.status_code, status);
362-
assert_eq!(data.status_message, message);
363-
});
358+
{
359+
let span = create_span();
360+
let status = StatusCode::Ok;
361+
let message = "OK".to_string();
362+
span.set_status(status, message);
363+
span.with_data(|data| {
364+
assert_eq!(data.status_code, status);
365+
assert_eq!(data.status_message, "");
366+
});
367+
}
368+
{
369+
let span = create_span();
370+
let status = StatusCode::Unset;
371+
let message = "OK".to_string();
372+
span.set_status(status, message);
373+
span.with_data(|data| {
374+
assert_eq!(data.status_code, status);
375+
assert_eq!(data.status_message, "");
376+
});
377+
}
378+
{
379+
let span = create_span();
380+
let status = StatusCode::Error;
381+
let message = "Error".to_string();
382+
span.set_status(status, message);
383+
span.with_data(|data| {
384+
assert_eq!(data.status_code, status);
385+
assert_eq!(data.status_message, "Error");
386+
});
387+
}
364388
}
365389

366390
#[test]

0 commit comments

Comments
 (0)