Skip to content

Commit

Permalink
subscriber: add span status fields (#1351)
Browse files Browse the repository at this point in the history
## Motivation

Allow users to set custom span status codes and messages to follow
opentelemetry semantic conventions.

## Solution

Support the status code and status message fields in
`OpenTelemetrySubscriber`

Proposal to close #1346
  • Loading branch information
aym-v authored and hawkw committed Apr 16, 2021
1 parent d43f0e7 commit 466221c
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 35 deletions.
117 changes: 83 additions & 34 deletions tracing-opentelemetry/src/layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ use tracing_subscriber::layer::Context;
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::Layer;

static SPAN_NAME_FIELD: &str = "otel.name";
static SPAN_KIND_FIELD: &str = "otel.kind";
const SPAN_NAME_FIELD: &str = "otel.name";
const SPAN_KIND_FIELD: &str = "otel.kind";
const SPAN_STATUS_CODE_FIELD: &str = "otel.status_code";
const SPAN_STATUS_MESSAGE_FIELD: &str = "otel.status_message";

/// An [OpenTelemetry] propagation layer for use in a project that uses
/// [tracing].
Expand Down Expand Up @@ -85,18 +87,22 @@ impl WithContext {
}

fn str_to_span_kind(s: &str) -> Option<otel::SpanKind> {
if s.eq_ignore_ascii_case("SERVER") {
Some(otel::SpanKind::Server)
} else if s.eq_ignore_ascii_case("CLIENT") {
Some(otel::SpanKind::Client)
} else if s.eq_ignore_ascii_case("PRODUCER") {
Some(otel::SpanKind::Producer)
} else if s.eq_ignore_ascii_case("CONSUMER") {
Some(otel::SpanKind::Consumer)
} else if s.eq_ignore_ascii_case("INTERNAL") {
Some(otel::SpanKind::Internal)
} else {
None
match s {
s if s.eq_ignore_ascii_case("server") => Some(otel::SpanKind::Server),
s if s.eq_ignore_ascii_case("client") => Some(otel::SpanKind::Client),
s if s.eq_ignore_ascii_case("producer") => Some(otel::SpanKind::Producer),
s if s.eq_ignore_ascii_case("consumer") => Some(otel::SpanKind::Consumer),
s if s.eq_ignore_ascii_case("internal") => Some(otel::SpanKind::Internal),
_ => None,
}
}

fn str_to_status_code(s: &str) -> Option<otel::StatusCode> {
match s {
s if s.eq_ignore_ascii_case("unset") => Some(otel::StatusCode::Unset),
s if s.eq_ignore_ascii_case("ok") => Some(otel::StatusCode::Ok),
s if s.eq_ignore_ascii_case("error") => Some(otel::StatusCode::Error),
_ => None,
}
}

Expand Down Expand Up @@ -200,16 +206,18 @@ impl<'a> field::Visit for SpanAttributeVisitor<'a> {
///
/// [`Span`]: opentelemetry::trace::Span
fn record_str(&mut self, field: &field::Field, value: &str) {
if field.name() == SPAN_NAME_FIELD {
self.0.name = value.to_string().into();
} else if field.name() == SPAN_KIND_FIELD {
self.0.span_kind = str_to_span_kind(value);
} else {
let attribute = KeyValue::new(field.name(), value.to_string());
if let Some(attributes) = &mut self.0.attributes {
attributes.push(attribute);
} else {
self.0.attributes = Some(vec![attribute]);
match field.name() {
SPAN_NAME_FIELD => self.0.name = value.to_string().into(),
SPAN_KIND_FIELD => self.0.span_kind = str_to_span_kind(value),
SPAN_STATUS_CODE_FIELD => self.0.status_code = str_to_status_code(value),
SPAN_STATUS_MESSAGE_FIELD => self.0.status_message = Some(value.to_owned()),
_ => {
let attribute = KeyValue::new(field.name(), value.to_string());
if let Some(attributes) = &mut self.0.attributes {
attributes.push(attribute);
} else {
self.0.attributes = Some(vec![attribute]);
}
}
}
}
Expand All @@ -219,16 +227,20 @@ impl<'a> field::Visit for SpanAttributeVisitor<'a> {
///
/// [`Span`]: opentelemetry::trace::Span
fn record_debug(&mut self, field: &field::Field, value: &dyn fmt::Debug) {
if field.name() == SPAN_NAME_FIELD {
self.0.name = format!("{:?}", value).into();
} else if field.name() == SPAN_KIND_FIELD {
self.0.span_kind = str_to_span_kind(&format!("{:?}", value));
} else {
let attribute = Key::new(field.name()).string(format!("{:?}", value));
if let Some(attributes) = &mut self.0.attributes {
attributes.push(attribute);
} else {
self.0.attributes = Some(vec![attribute]);
match field.name() {
SPAN_NAME_FIELD => self.0.name = format!("{:?}", value).into(),
SPAN_KIND_FIELD => self.0.span_kind = str_to_span_kind(&format!("{:?}", value)),
SPAN_STATUS_CODE_FIELD => {
self.0.status_code = str_to_status_code(&format!("{:?}", value))
}
SPAN_STATUS_MESSAGE_FIELD => self.0.status_message = Some(format!("{:?}", value)),
_ => {
let attribute = Key::new(field.name()).string(format!("{:?}", value));
if let Some(attributes) = &mut self.0.attributes {
attributes.push(attribute);
} else {
self.0.attributes = Some(vec![attribute]);
}
}
}
}
Expand Down Expand Up @@ -676,6 +688,43 @@ mod tests {
assert_eq!(recorded_kind, Some(otel::SpanKind::Server))
}

#[test]
fn span_status_code() {
let tracer = TestTracer(Arc::new(Mutex::new(None)));
let subscriber =
tracing_subscriber::registry().with(subscriber().with_tracer(tracer.clone()));

tracing::collect::with_default(subscriber, || {
tracing::debug_span!("request", otel.status_code = ?otel::StatusCode::Ok);
});
let recorded_status_code = tracer.0.lock().unwrap().as_ref().unwrap().status_code;
assert_eq!(recorded_status_code, Some(otel::StatusCode::Ok))
}

#[test]
fn span_status_message() {
let tracer = TestTracer(Arc::new(Mutex::new(None)));
let subscriber =
tracing_subscriber::registry().with(subscriber().with_tracer(tracer.clone()));

let message = "message";

tracing::collect::with_default(subscriber, || {
tracing::debug_span!("request", otel.status_message = message);
});

let recorded_status_message = tracer
.0
.lock()
.unwrap()
.as_ref()
.unwrap()
.status_message
.clone();

assert_eq!(recorded_status_message, Some(message.to_string()))
}

#[test]
fn trace_id_from_existing_context() {
let tracer = TestTracer(Arc::new(Mutex::new(None)));
Expand Down
3 changes: 3 additions & 0 deletions tracing-opentelemetry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@
//! Setting this field is useful if you want to display non-static information
//! in your span name.
//! * `otel.kind`: Set the span kind to one of the supported OpenTelemetry [span kinds].
//! * `otel.status_code`: Set the span status code to one of the supported OpenTelemetry [span status codes].
//! * `otel.status_message`: Set the span status message.
//!
//! [span kinds]: https://docs.rs/opentelemetry/latest/opentelemetry/trace/enum.SpanKind.html
//! [span status codes]: https://docs.rs/opentelemetry/latest/opentelemetry/trace/enum.StatusCode.html
//!
//! ### Semantic Conventions
//!
Expand Down
2 changes: 1 addition & 1 deletion tracing-opentelemetry/src/tracer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ impl otel::Span for CompatSpan {

fn set_status(&self, _code: otel::StatusCode, _message: String) {
#[cfg(debug_assertions)]
panic!("OpenTelemetry and tracing APIs cannot be mixed, use `tracing::span!` macro or `span.record()` instead.");
panic!("OpenTelemetry and tracing APIs cannot be mixed, use `tracing::span!` macro or `span.record()` with `otel.status_code` and `otel.status_message` instead.");
}

fn update_name(&self, _new_name: String) {
Expand Down

0 comments on commit 466221c

Please sign in to comment.