-
Notifications
You must be signed in to change notification settings - Fork 119
feat(spans): Equalize name and description for manual spans #6070
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
Changes from 3 commits
61a998e
5a42efc
03de602
01e0fff
18d7a6f
e7c7306
7dbff4a
3084fd3
14ab996
91bf9a6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -204,6 +204,7 @@ impl processing::Processor for SpansProcessor { | |
| }; | ||
|
|
||
| process::scrub(&mut spans, ctx); | ||
| process::backfill_description(&mut spans); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe |
||
|
|
||
| match dynamic_sampling::try_split_indexed_and_total(spans, ctx) { | ||
| Either::Left(spans) => Ok(Output::just(SpanOutput::TotalAndIndexed(spans))), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| use relay_conventions::attributes::*; | ||
| use relay_event_schema::protocol::Attributes; | ||
| use relay_protocol::{Annotated, Value}; | ||
|
|
||
| /// Derives a description for a V2 span, based on its name | ||
| /// and attributes. | ||
| /// | ||
| /// For now, this tries the following steps, in order: | ||
| /// - returns the span's name if its [`SENTRY__ORIGIN`] attribute is `"manual"` | ||
| /// - returns the span's [`DB__QUERY__TEXT`] attribute if it exists | ||
| /// - returns a combination of the span's [`HTTP__REQUEST__METHOD`] and | ||
| /// [`URL__FULL`] attributes, if they both exists. | ||
| /// | ||
| /// In the future, this logic will be partly moved to and extended in `sentry-conventions`. | ||
| pub fn derive_description_for_v2_span( | ||
| attributes: &Annotated<Attributes>, | ||
| name: &Annotated<String>, | ||
| ) -> Option<String> { | ||
| let attributes = attributes.value()?; | ||
|
|
||
| if attributes | ||
| .get_value(SENTRY__ORIGIN) | ||
| .and_then(|o| o.as_str()) | ||
| == Some("manual") | ||
|
Comment on lines
+19
to
+22
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This formatting is weird af |
||
| { | ||
| return name.value().cloned(); | ||
| } | ||
|
cursor[bot] marked this conversation as resolved.
cursor[bot] marked this conversation as resolved.
|
||
|
|
||
| if let Some(&Value::String(db_query)) = attributes.get_value(DB__QUERY__TEXT).as_ref() { | ||
| return Some(db_query.clone()); | ||
| } | ||
|
Comment on lines
+27
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Moving Suggested FixEnsure that derived span descriptions are generated before the PII scrubbing process runs. This would involve calling Prompt for AI Agent |
||
|
|
||
| if let Some(&Value::String(method)) = attributes.get_value(HTTP__REQUEST__METHOD).as_ref() | ||
| && let Some(&Value::String(url)) = attributes.get_value(URL__FULL).as_ref() | ||
| { | ||
| return Some(format!("{method} {url}")); | ||
| } | ||
|
|
||
| None | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,10 +1,20 @@ | ||||||
| use relay_conventions::attributes::SENTRY__OP; | ||||||
| use relay_conventions::attributes::{SENTRY__DESCRIPTION, SENTRY__OP, SENTRY__ORIGIN}; | ||||||
| use relay_conventions::name_for_op_and_attributes; | ||||||
| use relay_event_schema::protocol::{Attributes, Span}; | ||||||
| use relay_protocol::{Getter, GetterIter, Val}; | ||||||
|
|
||||||
| /// Constructs a name attribute for a span, following the rules defined in sentry-conventions. | ||||||
| /// Constructs a name attribute for a V1 span. | ||||||
| /// | ||||||
| /// If the span's origin is `"manual"`, its description is used as the name. | ||||||
| /// Otherwise, the name is constructed following the rules defined in sentry-conventions. | ||||||
| pub fn name_for_span(span: &Span) -> Option<String> { | ||||||
| let origin = span.origin.value().map(|o| o.as_str()); | ||||||
| let description = span.description.value().map(|d| d.as_str()); | ||||||
|
|
||||||
| if let Some(name) = name_for_origin_and_description(origin, description) { | ||||||
| return Some(name); | ||||||
| } | ||||||
|
|
||||||
| let op = span.op.value()?; | ||||||
|
|
||||||
| let Some(data) = span.data.value() else { | ||||||
|
|
@@ -20,12 +30,38 @@ pub fn name_for_span(span: &Span) -> Option<String> { | |||||
| )) | ||||||
| } | ||||||
|
|
||||||
| /// Constructs a name attribute for a span, following the rules defined in sentry-conventions. | ||||||
| /// Constructs a name attribute for a V2 span, based on its attributes. | ||||||
| /// | ||||||
| /// If the attributes contain [`SENTRY__ORIGIN`] with the value `"manual"`, | ||||||
| /// the description (contained in [`SENTRY__DESCRIPTION`]) is used as the name. | ||||||
| /// Otherwise, the name is constructed following the rules defined in sentry-conventions. | ||||||
| pub fn name_for_attributes(attributes: &Attributes) -> Option<String> { | ||||||
| let origin = attributes | ||||||
| .get_value(SENTRY__ORIGIN) | ||||||
| .and_then(|o| o.as_str()); | ||||||
| let description = attributes | ||||||
| .get_value(SENTRY__DESCRIPTION) | ||||||
| .and_then(|d| d.as_str()); | ||||||
|
|
||||||
| if let Some(name) = name_for_origin_and_description(origin, description) { | ||||||
| return Some(name); | ||||||
| } | ||||||
|
|
||||||
| let op = attributes.get_value(SENTRY__OP)?.as_str()?; | ||||||
| Some(name_for_op_and_attributes(op, &AttributeGetter(attributes))) | ||||||
| } | ||||||
|
|
||||||
| fn name_for_origin_and_description( | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call. |
||||||
| origin: Option<&str>, | ||||||
| description: Option<&str>, | ||||||
| ) -> Option<String> { | ||||||
| if origin == Some("manual") { | ||||||
| description.map(String::from) | ||||||
| } else { | ||||||
| None | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| struct EmptyGetter {} | ||||||
|
|
||||||
| impl Getter for EmptyGetter { | ||||||
|
|
@@ -203,4 +239,67 @@ mod tests { | |||||
| Some("Database operation".to_owned()) | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| #[test] | ||||||
| fn test_manual_spans_use_description_v1() { | ||||||
| let span = Span { | ||||||
| origin: Annotated::new("manual".to_owned()), | ||||||
| description: Annotated::new("Custom name".to_owned()), | ||||||
| op: Annotated::new("db".to_owned()), | ||||||
| data: Annotated::new(SpanData { | ||||||
| other: Object::from([ | ||||||
| ( | ||||||
| "db.query.summary".to_owned(), | ||||||
| Value::String("SELECT users".to_owned()).into(), | ||||||
| ), | ||||||
| ( | ||||||
| "db.operation.name".to_owned(), | ||||||
| Value::String("INSERT".to_owned()).into(), | ||||||
| ), | ||||||
| ( | ||||||
| "db.collection.name".to_owned(), | ||||||
| Value::String("widgets".to_owned()).into(), | ||||||
| ), | ||||||
| ]), | ||||||
| ..Default::default() | ||||||
| }), | ||||||
| ..Default::default() | ||||||
| }; | ||||||
| assert_eq!(name_for_span(&span), Some("Custom name".to_owned())); | ||||||
| } | ||||||
|
|
||||||
| #[test] | ||||||
| fn test_manual_spans_use_description_v2() { | ||||||
| let attributes = Attributes::from([ | ||||||
| ( | ||||||
| "sentry.origin".to_owned(), | ||||||
| Annotated::new("manual".to_owned().into()), | ||||||
| ), | ||||||
| ( | ||||||
| "sentry.description".to_owned(), | ||||||
| Annotated::new("Custom name".to_owned().into()), | ||||||
| ), | ||||||
| ( | ||||||
| "sentry.op".to_owned(), | ||||||
| Annotated::new("db".to_owned().into()), | ||||||
| ), | ||||||
| ( | ||||||
| "db.query.summary".to_owned(), | ||||||
| Annotated::new("SELECT users".to_owned().into()), | ||||||
| ), | ||||||
| ( | ||||||
| "db.operation.name".to_owned(), | ||||||
| Annotated::new("INSERT".to_owned().into()), | ||||||
| ), | ||||||
| ( | ||||||
| "db.collection.name".to_owned(), | ||||||
| Annotated::new("widgets".to_owned().into()), | ||||||
| ), | ||||||
| ]); | ||||||
|
|
||||||
| assert_eq!( | ||||||
| name_for_attributes(&attributes), | ||||||
| Some("Custom name".to_owned()) | ||||||
| ); | ||||||
| } | ||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might need a check which doesn't just use
value()but also considers meta,Emptytrait might be able to do that for you.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean check if
attrscontainsSENTRY__DESCRIPTIONwith a nonempty value?