Skip to content

Commit bddef29

Browse files
authored
Add support for instrumented functions which return Result (#28)
## Motivation Currently, the tracing instrumentation macro emits a single event after the function call, but in the current span, with just a field named error set. For example: ```rust #[instrument(err)] fn test() -> Result<(), ()> { ...body } ``` gets roughly expanded to ```rust fn test() -> Result<(), ()> { let span = span!("test") fn inner() -> Result<(), ()> { ...body } match inner() { Ok(x) => Ok(x), Err(err) => { error!(error=%err) Err(err) } } ``` In the error case of the result, the macro will emit an error level event with just an `error` field set to the display (or debug) value of the returned error. While there exists support for the Error primitive in tracing, the primitive only supports 'static Errors. See tokio-rs/tracing#1308 ## Solution This PR adds support to use this event to fill the span status error description with the content of the error field of this event. Additionally, this ass support to emit these events (or manually created ones that follow the same format) as OTel events following the exception convention. The operation is optional and can be configured using the `ErrorFieldConfig`. This seems like another hack similar to `otel.*` fields, but should reduce some boilerplate in existing codebases. I propose to keep this until `tracing` improves support for Error fields.
1 parent 1c61ea6 commit bddef29

File tree

3 files changed

+611
-29
lines changed

3 files changed

+611
-29
lines changed

examples/opentelemetry-error.rs

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
use std::{
2+
borrow::Cow,
3+
error::Error as StdError,
4+
fmt::{Debug, Display},
5+
io::Write,
6+
thread,
7+
time::{Duration, SystemTime},
8+
};
9+
10+
use opentelemetry::{
11+
global,
12+
sdk::{
13+
self,
14+
export::trace::{ExportResult, SpanExporter},
15+
},
16+
trace::TracerProvider,
17+
};
18+
use tracing::{error, instrument, span, trace, warn};
19+
use tracing_subscriber::prelude::*;
20+
21+
#[derive(Debug)]
22+
enum Error {
23+
ErrorQueryPassed,
24+
}
25+
26+
impl StdError for Error {}
27+
28+
impl Display for Error {
29+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30+
match self {
31+
Error::ErrorQueryPassed => write!(f, "Encountered the error flag in the query"),
32+
}
33+
}
34+
}
35+
36+
#[instrument(err)]
37+
fn failable_work(fail: bool) -> Result<&'static str, Error> {
38+
span!(tracing::Level::INFO, "expensive_step_1")
39+
.in_scope(|| thread::sleep(Duration::from_millis(25)));
40+
span!(tracing::Level::INFO, "expensive_step_2")
41+
.in_scope(|| thread::sleep(Duration::from_millis(25)));
42+
43+
if fail {
44+
return Err(Error::ErrorQueryPassed);
45+
}
46+
Ok("success")
47+
}
48+
49+
#[instrument(err)]
50+
fn double_failable_work(fail: bool) -> Result<&'static str, Error> {
51+
span!(tracing::Level::INFO, "expensive_step_1")
52+
.in_scope(|| thread::sleep(Duration::from_millis(25)));
53+
span!(tracing::Level::INFO, "expensive_step_2")
54+
.in_scope(|| thread::sleep(Duration::from_millis(25)));
55+
error!(error = "test", "hello");
56+
if fail {
57+
return Err(Error::ErrorQueryPassed);
58+
}
59+
Ok("success")
60+
}
61+
62+
fn main() -> Result<(), Box<dyn StdError + Send + Sync + 'static>> {
63+
let builder = sdk::trace::TracerProvider::builder().with_simple_exporter(WriterExporter);
64+
let provider = builder.build();
65+
let tracer = provider.versioned_tracer(
66+
"opentelemetry-write-exporter",
67+
None::<Cow<'static, str>>,
68+
None::<Cow<'static, str>>,
69+
None,
70+
);
71+
global::set_tracer_provider(provider);
72+
73+
let opentelemetry = tracing_opentelemetry::layer().with_tracer(tracer);
74+
tracing_subscriber::registry()
75+
.with(opentelemetry)
76+
.try_init()?;
77+
78+
{
79+
let root = span!(tracing::Level::INFO, "app_start", work_units = 2);
80+
let _enter = root.enter();
81+
82+
let work_result = failable_work(false);
83+
84+
trace!("status: {}", work_result.unwrap());
85+
let work_result = failable_work(true);
86+
87+
trace!("status: {}", work_result.err().unwrap());
88+
warn!("About to exit!");
89+
90+
let _ = double_failable_work(true);
91+
} // Once this scope is closed, all spans inside are closed as well
92+
93+
// Shut down the current tracer provider. This will invoke the shutdown
94+
// method on all span processors. span processors should export remaining
95+
// spans before return.
96+
global::shutdown_tracer_provider();
97+
98+
Ok(())
99+
}
100+
101+
#[derive(Debug)]
102+
struct WriterExporter;
103+
104+
impl SpanExporter for WriterExporter {
105+
fn export(
106+
&mut self,
107+
batch: Vec<opentelemetry::sdk::export::trace::SpanData>,
108+
) -> futures_util::future::BoxFuture<'static, opentelemetry::sdk::export::trace::ExportResult>
109+
{
110+
let mut writer = std::io::stdout();
111+
for span in batch {
112+
writeln!(writer, "{}", SpanData(span)).unwrap();
113+
}
114+
writeln!(writer).unwrap();
115+
116+
Box::pin(async move { ExportResult::Ok(()) })
117+
}
118+
}
119+
120+
struct SpanData(opentelemetry::sdk::export::trace::SpanData);
121+
impl Display for SpanData {
122+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123+
writeln!(f, "Span: \"{}\"", self.0.name)?;
124+
match &self.0.status {
125+
opentelemetry::trace::Status::Unset => {}
126+
opentelemetry::trace::Status::Error { description } => {
127+
writeln!(f, "- Status: Error")?;
128+
writeln!(f, "- Error: {description}")?
129+
}
130+
opentelemetry::trace::Status::Ok => writeln!(f, "- Status: Ok")?,
131+
}
132+
writeln!(
133+
f,
134+
"- Start: {}",
135+
self.0
136+
.start_time
137+
.duration_since(SystemTime::UNIX_EPOCH)
138+
.expect("start time is before the unix epoch")
139+
.as_secs()
140+
)?;
141+
writeln!(
142+
f,
143+
"- End: {}",
144+
self.0
145+
.end_time
146+
.duration_since(SystemTime::UNIX_EPOCH)
147+
.expect("end time is before the unix epoch")
148+
.as_secs()
149+
)?;
150+
writeln!(f, "- Resource:")?;
151+
for (k, v) in self.0.resource.iter() {
152+
writeln!(f, " - {}: {}", k, v)?;
153+
}
154+
writeln!(f, "- Attributes:")?;
155+
for (k, v) in self.0.attributes.iter() {
156+
writeln!(f, " - {}: {}", k, v)?;
157+
}
158+
159+
writeln!(f, "- Events:")?;
160+
for event in self.0.events.iter() {
161+
if let Some(error) =
162+
event
163+
.attributes
164+
.iter()
165+
.fold(Option::<String>::None, |mut acc, d| {
166+
if let Some(mut acc) = acc.take() {
167+
use std::fmt::Write;
168+
let _ = write!(acc, ", {}={}", d.key, d.value);
169+
Some(acc)
170+
} else {
171+
Some(format!("{} = {}", d.key, d.value))
172+
}
173+
})
174+
{
175+
writeln!(f, " - \"{}\" {{{error}}}", event.name)?;
176+
} else {
177+
writeln!(f, " - \"{}\"", event.name)?;
178+
}
179+
}
180+
writeln!(f, "- Links:")?;
181+
for link in self.0.links.iter() {
182+
writeln!(f, " - {:?}", link)?;
183+
}
184+
Ok(())
185+
}
186+
}

0 commit comments

Comments
 (0)