Skip to content

Commit

Permalink
Merge pull request #159 from emit-rs/feat/exception.stacktrace
Browse files Browse the repository at this point in the history
Write error source chain to exception.stacktrace if present
  • Loading branch information
KodrAus authored Oct 23, 2024
2 parents 7c95b01 + 78c8da7 commit 4a20dd4
Show file tree
Hide file tree
Showing 7 changed files with 362 additions and 54 deletions.
65 changes: 43 additions & 22 deletions emitter/otlp/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,45 +281,66 @@ pub(crate) fn stream_field<'sval, S: sval::Stream<'sval> + ?Sized>(
stream.record_tuple_value_end(None, label, index)
}

pub(crate) fn stream_attributes<'sval>(
stream: &mut (impl sval::Stream<'sval> + ?Sized),
pub(crate) fn stream_attributes<'sval, S: sval::Stream<'sval> + ?Sized>(
stream: &mut S,
props: &'sval impl emit::props::Props,
mut for_each: impl FnMut(
AttributeStream<'_, S>,
emit::str::Str<'sval>,
emit::value::Value<'sval>,
) -> Option<(emit::str::Str<'sval>, emit::value::Value<'sval>)>,
) -> sval::Result,
) -> sval::Result {
stream.seq_begin(None)?;

props.dedup().for_each(|k, v| {
if let Some((k, v)) = for_each(k, v) {
stream
.seq_value_begin()
.map(|_| ControlFlow::Continue(()))
.unwrap_or(ControlFlow::Break(()))?;

sval_ref::stream_ref(
&mut *stream,
KeyValue {
key: k,
value: EmitValue(v),
},
)
for_each(AttributeStream(&mut *stream), k, v)
.map(|_| ControlFlow::Continue(()))
.unwrap_or(ControlFlow::Break(()))?;

stream
.seq_value_end()
.map(|_| ControlFlow::Continue(()))
.unwrap_or(ControlFlow::Break(()))?;
}

ControlFlow::Continue(())
});

stream.seq_end()
}

pub(crate) struct AttributeStream<'a, S: ?Sized>(&'a mut S);

impl<'a, 'sval, S: sval::Stream<'sval> + ?Sized> AttributeStream<'a, S> {
pub(crate) fn stream_attribute(
&mut self,
key: emit::str::Str<'sval>,
value: emit::value::Value<'sval>,
) -> sval::Result {
self.0.seq_value_begin()?;

sval_ref::stream_ref(
&mut *self.0,
KeyValue {
key,
value: EmitValue(value),
},
)?;

self.0.seq_value_end()?;

Ok(())
}

pub(crate) fn stream_custom_attribute_computed(
&mut self,
key: emit::str::Str<'_>,
value: impl sval::Value,
) -> sval::Result {
self.0.seq_value_begin()?;

self.0.value_computed(&KeyValue { key, value })?;

self.0.seq_value_end()?;

Ok(())
}
}

pub(crate) type MessageFormatter = dyn Fn(&emit::event::Event<&dyn emit::props::ErasedProps>, &mut fmt::Formatter) -> fmt::Result
+ Send
+ Sync;
Expand Down
52 changes: 52 additions & 0 deletions emitter/otlp/src/data/any_value.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt;

use sval_derive::Value;

use super::stream_field;
Expand Down Expand Up @@ -92,6 +94,56 @@ impl<'a, K: sval_ref::ValueRef<'a>, V: sval_ref::ValueRef<'a>> sval_ref::ValueRe
}
}

#[repr(transparent)]
pub struct Stacktrace(dyn std::error::Error + 'static);

impl Stacktrace {
pub fn new_borrowed<'a>(err: &'a (dyn std::error::Error + 'static)) -> &'a Self {
// SAFETY: `Stacktrace` and `dyn std::error::Error + 'static` have the same ABI
unsafe { &*(err as *const (dyn std::error::Error + 'static) as *const Stacktrace) }
}
}

impl fmt::Display for Stacktrace {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut first = true;

for cause in std::iter::successors(Some(&self.0), |err| (*err).source()) {
if !first {
f.write_str("\n")?;
}
first = false;

f.write_str("caused by: ")?;
fmt::Display::fmt(cause, f)?;
}

Ok(())
}
}

pub struct TextValue<T>(pub T);

impl<T: fmt::Display> sval::Value for TextValue<T> {
fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
stream.enum_begin(None, None, None)?;
stream.tagged_begin(
None,
Some(&ANY_VALUE_STRING_LABEL),
Some(&ANY_VALUE_STRING_INDEX),
)?;

sval::stream_display(&mut *stream, &self.0)?;

stream.tagged_end(
None,
Some(&ANY_VALUE_STRING_LABEL),
Some(&ANY_VALUE_STRING_INDEX),
)?;
stream.enum_end(None, None, None)
}
}

pub struct EmitValue<'a>(pub emit::value::Value<'a>);

impl<'a> sval::Value for EmitValue<'a> {
Expand Down
67 changes: 66 additions & 1 deletion emitter/otlp/src/data/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,77 @@ mod tests {
}

#[test]
fn encode_err() {
fn encode_err_str() {
encode_event::<LogsEventEncoder>(emit::evt!("failed: {err}", err: "test"), |buf| {
let de = logs::LogRecord::decode(buf).unwrap();

assert_eq!(1, de.attributes.len());

assert_eq!("exception.message", de.attributes[0].key);
assert_eq!(Some(string_value("test")), de.attributes[0].value);
});
}

#[test]
fn encode_err_stacktrace() {
#[derive(Debug)]
struct Error {
msg: &'static str,
source: Option<Box<dyn std::error::Error + 'static>>,
}

impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Display::fmt(self.msg, f)
}
}

impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source
.as_ref()
.map(|source| &**source)
}
}

let err = Error {
msg: "something went wrong",
source: Some(Box::new(Error {
msg: "there was a problem",
source: Some(Box::new(std::io::Error::new(std::io::ErrorKind::Other, "IO error"))),
})),
};

encode_event::<LogsEventEncoder>(emit::evt!("failed: {err}", err), |buf| {
let de = logs::LogRecord::decode(buf).unwrap();

assert_eq!(2, de.attributes.len());

assert_eq!("exception.stacktrace", de.attributes[0].key);
assert_eq!(
Some(string_value("caused by: there was a problem\ncaused by: IO error")),
de.attributes[0].value
);

assert_eq!("exception.message", de.attributes[1].key);
assert_eq!(
Some(string_value("something went wrong")),
de.attributes[1].value
);
});

let err = Error { msg: "something went wrong", source: None };

encode_event::<LogsEventEncoder>(emit::evt!("failed: {err}", err), |buf| {
let de = logs::LogRecord::decode(buf).unwrap();

assert_eq!(1, de.attributes.len());

assert_eq!("exception.message", de.attributes[0].key);
assert_eq!(
Some(string_value("something went wrong")),
de.attributes[0].value
);
});
}
}
26 changes: 19 additions & 7 deletions emitter/otlp/src/data/logs/log_record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::marker::PhantomData;

use sval_derive::Value;

use crate::data::{stream_attributes, stream_field, AnyValue, KeyValue};
use crate::data::{stream_attributes, stream_field, AnyValue, KeyValue, Stacktrace, TextValue};

#[derive(Value)]
#[repr(i32)]
Expand Down Expand Up @@ -95,27 +95,39 @@ impl<
&LOG_RECORD_ATTRIBUTES_LABEL,
&LOG_RECORD_ATTRIBUTES_INDEX,
|stream| {
stream_attributes(stream, &self.0, |k, v| match k.get() {
stream_attributes(stream, &self.0, |mut stream, k, v| match k.get() {
emit::well_known::KEY_LVL => {
level = v.by_ref().cast::<emit::Level>().unwrap_or_default();
None
Ok(())
}
emit::well_known::KEY_SPAN_ID => {
span_id = v
.by_ref()
.cast::<emit::SpanId>()
.map(|span_id| SP::from(span_id));
None
Ok(())
}
emit::well_known::KEY_TRACE_ID => {
trace_id = v
.by_ref()
.cast::<emit::TraceId>()
.map(|trace_id| TR::from(trace_id));
None
Ok(())
}
emit::well_known::KEY_ERR => Some((emit::Str::new("exception.message"), v)),
_ => Some((k, v)),
emit::well_known::KEY_ERR => {
// If the error has a cause chain then write it into the exception.stacktrace attribute
if let Some(cause) = v.to_borrowed_error().and_then(|err| err.source()) {
stream.stream_custom_attribute_computed(
emit::Str::new("exception.stacktrace"),
TextValue(Stacktrace::new_borrowed(cause)),
)?;
}

stream.stream_attribute(emit::Str::new("exception.message"), v)?;

Ok(())
}
_ => stream.stream_attribute(k, v),
})
},
)?;
Expand Down
6 changes: 5 additions & 1 deletion emitter/otlp/src/data/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ impl<P: emit::props::Props> sval::Value for PropsResourceAttributes<P> {
&mut *stream,
&RESOURCE_ATTRIBUTES_LABEL,
&RESOURCE_ATTRIBUTES_INDEX,
|stream| stream_attributes(stream, &self.0, |k, v| Some((k, v))),
|stream| {
stream_attributes(stream, &self.0, |mut stream, k, v| {
stream.stream_attribute(k, v)
})
},
)?;

stream.record_tuple_end(None, None, None)
Expand Down
Loading

0 comments on commit 4a20dd4

Please sign in to comment.