Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Errors are silently discarded if tracing event gets converted to a message and in breadcrumbs #702

Open
thomaseizinger opened this issue Oct 28, 2024 · 9 comments

Comments

@thomaseizinger
Copy link
Contributor

Environment

  • Version 0.34.0

Steps to Reproduce

  1. Create an event-filter that maps WARN logs to Sentry events: https://github.com/firezone/firezone/blob/51d92265f4163967c0045f67495954515da15c62/rust/logging/src/lib.rs#L102-L115
  2. Attach errors as &dyn std::error::Error in a WARN log as advised in the documentation: https://github.com/firezone/firezone/blob/51d92265f4163967c0045f67495954515da15c62/rust/gateway/src/eventloop.rs#L80

Expected Result

To receive a Sentry event that contains the error.

Actual Result

A Sentry event that only contains the log message but not the error:

Image


I've been digging through the source code here and the problem is that when a tracing event gets converted to a Sentry event, all fields apart from what is prefixed with tags. gets silently dropped:

/// Creates an [`Event`] from a given [`tracing_core::Event`]
pub fn event_from_event<'context, S>(
event: &tracing_core::Event,
ctx: impl Into<Option<Context<'context, S>>>,
) -> Event<'static>
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
let (message, mut visitor) = extract_event_data_with_context(event, ctx.into());
Event {
logger: Some(event.metadata().target().to_owned()),
level: convert_tracing_level(event.metadata().level()),
message,
tags: tags_from_event(&mut visitor.json_values),
contexts: contexts_from_event(event, visitor.json_values),
..Default::default()
}
}

This is pretty confusing because the documentation clearly states:

To track error structs, assign a reference to error trait object as field in one of the logging macros. By convention, it is recommended to use the ERROR level and assign it to a field called error, although the integration will also work with all other levels and field names.

What is perhaps missing here is a foot-note that it will work with all levels as long as you configure an event filter that converts the event to a sentry exception! However, instead of fixing the documentation here, I think the proper fix is to actually make this work the way the documentation states and attach the exception to the message, even if the log level is not ERROR.

@thomaseizinger
Copy link
Contributor Author

However, instead of fixing the documentation here, I think the proper fix is to actually make this work the way the documentation states and attach the exception to the message, even if the log level is not ERROR.

I tried to fix this but realised that this essentially just makes "messages" and "exceptions" the same?

@thomaseizinger
Copy link
Contributor Author

However, instead of fixing the documentation here, I think the proper fix is to actually make this work the way the documentation states and attach the exception to the message, even if the log level is not ERROR.

I tried to fix this but realised that this essentially just makes "messages" and "exceptions" the same?

The other issue here is that the field does not become a context because it is recorded in the FieldVisitor as an exceptions and therefore not grouped with the other fields.

github-merge-queue bot pushed a commit to firezone/firezone that referenced this issue Oct 28, 2024
It appears that I have misunderstood the documentation of
`sentry-tracing`. When a message gets logged as an event (rather than an
"exception") `std::error::Error`s attached as tracing `Value`s do not
get recorded. It doesn't really matter whether we record our events as
exceptions or messages. We should ideally look at all of them and
particularly noisy ones can be muted forever in Sentry so we don't end
up in a "boy who cried wolf" situation. Therefore, this PR changes our
event filter to also submit WARNs as exceptions to make sure they get
logged accordingly.

Resolves: #7161.
Related: getsentry/sentry-rust#702.
@DoumanAsh
Copy link
Contributor

I think everything is geared towards errors being treated as exceptions hence non-exception events are treated as informational
I usually use empty span fields to record error information and keep context via https://docs.rs/sentry-tracing/0.34.0/sentry_tracing/struct.SentryLayer.html#method.enable_span_attributes

@thomaseizinger
Copy link
Contributor Author

This is also a problem for breadcrumbs. There, any recorded "errors" on the way are also discarded because only the json_values are captured, see

/// Creates a [`Breadcrumb`] from a given [`tracing_core::Event`]
pub fn breadcrumb_from_event(event: &tracing_core::Event) -> Breadcrumb {
let (message, visitor) = extract_event_data(event);
Breadcrumb {
category: Some(event.metadata().target().to_owned()),
ty: "log".into(),
level: convert_tracing_level(event.metadata().level()),
message,
data: visitor.json_values,
..Default::default()
}
}
.

The real foot-gun here is that this basically makes it really dangerous to apply a blanket rule of "we will just record all errors as Error within tracing". Arguably, that is what applications should do in order to preserve as much type-information as possible for as long as possible. Sentry discourages that because it will silently drop all of them unless they get reported as exceptions to Sentry.

@DoumanAsh
Copy link
Contributor

You do not necessary need to record everything in error
As I mentioned you can use span's fields (initially initialized with tracing::field::Emtpy) and the fill it on demand

@thomaseizinger
Copy link
Contributor Author

You do not necessary need to record everything in error

Why wouldn't I though? This is like saying: "You know need to pass fields to tracing, just format everything as a string". Yeah I can do that, but what is the point of using a library that supported structured logging then? I would like to record errors as std::error::Error and Sentry should walk through the source chain and display all of them.

@DoumanAsh
Copy link
Contributor

I believe you're not listening to me
What I mean is that if you do not want to aggregate everything tracing::error! you can do it as follows:

#[tracing::insturment(fields(extr_error = tracing::field::Empy))]
fn my_work() {
   tracing::Span::current().record("extr_error", "whatever I want");
   tracing::error!("my error");
}

This is just suggestion if you want to make current layer work for you better
But otherwise you can continue with issue

@thomaseizinger
Copy link
Contributor Author

Unless I am missing something, this doesn't solve the issue though, does it? I haven't tried it but from reading the code, the following should happen:

  1. Errors set on the span are recorded as debug because SpanFieldVisitor doesn't override the default implementation of record_error.
  2. Additional fields recorded in the span later ignore all std::error::Errors:
    for (key, value) in data.json_values {
    span.set_data(&key, value);
    }

if you do not want to aggregate everything tracing::error!

What I want to do is record information in a tracing-idiomatic way without having to implement workarounds because sentry-tracing decides to silently drop certain fields.

@DoumanAsh
Copy link
Contributor

This will work under assumption you use https://docs.rs/sentry-tracing/0.34.0/sentry_tracing/struct.SentryLayer.html#method.enable_span_attributes because it should attach span fields associated with the event (but not to breadcrumb)

But yes, it is not ideal that event doesn't record all non tag fields

github-merge-queue bot pushed a commit to firezone/firezone that referenced this issue Nov 12, 2024
Our logging library `tracing` supports structured logging. Structured
logging means we can include values within a `tracing::Event` without
having to immediately format it as a string. Processing these values -
such as errors - as their original type allows the various `tracing`
layers to capture and represent them as they see fit.

One of these layers is responsible for sending ERROR and WARN events to
Sentry, as part of which `std::error::Error` values get automatically
captured as so-called "sentry exceptions".

Unfortunately, there is a caveat: If an `std::error::Error` value is
included in an event that does not get mapped to an exception, the
`error` field is completely lost. See
getsentry/sentry-rust#702 for details.

To work around this, we introduce a `err_with_sources` adapter that an
error and all its sources together into a string. For all
`tracing::debug!` statements, we then use this to report these errors.

It is really unfortunate that we have to do this and cannot use the same
mechanism, regardless of the log level. However, until this is fixed
upstream, this will do and gives us better information in the log
submitted to Sentry.
@thomaseizinger thomaseizinger changed the title Errors are silently discarded if tracing event gets converted to a message Errors are silently discarded if tracing event gets converted to a message or in breadcrumbs Nov 12, 2024
@thomaseizinger thomaseizinger changed the title Errors are silently discarded if tracing event gets converted to a message or in breadcrumbs Errors are silently discarded if tracing event gets converted to a message and in breadcrumbs Nov 12, 2024
github-merge-queue bot pushed a commit to firezone/firezone that referenced this issue Nov 18, 2024
Due to getsentry/sentry-rust#702, errors which
are embedded as `tracing::Value` unfortunately get silently discarded
when reported as part of Sentry "Event"s and not "Exception"s.

The design idea of these telemetry events is that they aren't fatal
errors so we don't need to treat them with the highest priority. They
may also appear quite often, so to save performance and bandwidth, we
sample them at a rate of 1% at creation time.

In order to not lose the context of these errors, we instead format them
into the message. This makes them completely identical to the `debug!`
logs which we have on every call-site of `telemetry_event!` which
prompted me to make that implicit as part of creating the
`telemetry_event!`.

Resolves: #7343.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants