mirror of
https://github.com/tokio-rs/tracing.git
synced 2025-09-30 22:40:34 +00:00
subscriber: add span status fields (#1351)
## 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
This commit is contained in:
parent
3d65a48526
commit
b3490ffc76
@ -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].
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)));
|
||||
|
@ -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
|
||||
//!
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user