-
Notifications
You must be signed in to change notification settings - Fork 104
feat(eap): Normalize deprecated attributes #5257
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 7 commits
70756e8
95de740
aeea4a3
9d485d4
61e6a13
43b10d4
273078b
8d5bf19
953cf3b
2c907f0
7514845
82f8041
982bef1
7ae5f0b
1303910
0ad2495
6b00628
c4459f2
5dfeff0
508887b
9552f46
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 |
|---|---|---|
|
|
@@ -5,11 +5,11 @@ | |
| use chrono::{DateTime, Utc}; | ||
| use relay_common::time::UnixTimestamp; | ||
| use relay_conventions::{ | ||
| BROWSER_NAME, BROWSER_VERSION, OBSERVED_TIMESTAMP_NANOS, USER_GEO_CITY, USER_GEO_COUNTRY_CODE, | ||
| USER_GEO_REGION, USER_GEO_SUBDIVISION, | ||
| AttributeInfo, BROWSER_NAME, BROWSER_VERSION, OBSERVED_TIMESTAMP_NANOS, USER_GEO_CITY, | ||
| USER_GEO_COUNTRY_CODE, USER_GEO_REGION, USER_GEO_SUBDIVISION, WriteBehavior, | ||
| }; | ||
| use relay_event_schema::protocol::{AttributeType, Attributes, BrowserContext, Geo}; | ||
| use relay_protocol::{Annotated, ErrorKind, Value}; | ||
| use relay_protocol::{Annotated, ErrorKind, Remark, RemarkType, Value}; | ||
|
|
||
| use crate::{ClientHints, FromUserAgentInfo as _, RawUserAgentInfo}; | ||
|
|
||
|
|
@@ -132,6 +132,58 @@ pub fn normalize_user_geo( | |
| attributes.insert_if_missing(USER_GEO_REGION, || geo.region); | ||
| } | ||
|
|
||
| /// Normalizes deprecated attributes according to `sentry-conventions`. | ||
| /// | ||
| /// Attributes with a status of `"normalize"` will be moved to their replacement name. | ||
| /// If there is already a value present under the replacement name, it will be left alone, | ||
| /// but the deprecated attribute is removed anyway. | ||
| /// | ||
| /// Attributes with a status of `"backfill"` will be copied to their replacement name if the | ||
| /// replacement name is not present. In any case, the original name is left alone. | ||
| pub fn normalize_attribute_names(attributes: &mut Annotated<Attributes>) { | ||
| normalize_attribute_names_inner(attributes, relay_conventions::attribute_info) | ||
| } | ||
|
|
||
| fn normalize_attribute_names_inner( | ||
| attributes: &mut Annotated<Attributes>, | ||
| attribute_info: fn(&str) -> Option<&'static AttributeInfo>, | ||
| ) { | ||
| let attributes = attributes.get_or_insert_with(Default::default); | ||
| let attribute_names: Vec<_> = attributes.keys().cloned().collect(); | ||
|
|
||
| for name in attribute_names { | ||
| let Some(attribute_info) = attribute_info(&name) else { | ||
| continue; | ||
| }; | ||
|
|
||
| match attribute_info.write_behavior { | ||
| WriteBehavior::CurrentName => continue, | ||
| WriteBehavior::NewName(new_name) => { | ||
| let Some(old_attribute) = attributes.get_raw_mut(&name) else { | ||
| continue; | ||
| }; | ||
|
|
||
| let new_attribute = old_attribute.clone(); | ||
loewenheim marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| old_attribute.set_value(None); | ||
| old_attribute | ||
| .meta_mut() | ||
| .add_remark(Remark::new(RemarkType::Removed, "attribute.deprecated")); | ||
|
||
|
|
||
| if !attributes.contains_key(new_name) { | ||
| attributes.insert_raw(new_name.to_owned(), new_attribute); | ||
| } | ||
| } | ||
| WriteBehavior::BothNames(new_name) => { | ||
| if !attributes.contains_key(new_name) | ||
| && let Some(current_attribute) = attributes.get_raw(&name).cloned() | ||
| { | ||
| attributes.insert_raw(new_name.to_owned(), current_attribute); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use relay_protocol::SerializableAnnotated; | ||
|
|
@@ -464,4 +516,117 @@ mod tests { | |
| "#, | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_normalize_attributes() { | ||
| fn mock_attribute_info(name: &str) -> Option<&'static AttributeInfo> { | ||
| use relay_conventions::Pii; | ||
|
|
||
| match name { | ||
| "replace.empty" => Some(&AttributeInfo { | ||
| write_behavior: WriteBehavior::NewName("replaced"), | ||
| pii: Pii::Maybe, | ||
| aliases: &["replaced"], | ||
| }), | ||
| "replace.existing" => Some(&AttributeInfo { | ||
| write_behavior: WriteBehavior::NewName("not.replaced"), | ||
| pii: Pii::Maybe, | ||
| aliases: &["not.replaced"], | ||
| }), | ||
| "backfill.empty" => Some(&AttributeInfo { | ||
| write_behavior: WriteBehavior::BothNames("backfilled"), | ||
| pii: Pii::Maybe, | ||
| aliases: &["backfilled"], | ||
| }), | ||
| "backfill.existing" => Some(&AttributeInfo { | ||
| write_behavior: WriteBehavior::BothNames("not.backfilled"), | ||
| pii: Pii::Maybe, | ||
| aliases: &["not.backfilled"], | ||
| }), | ||
| _ => None, | ||
| } | ||
| } | ||
|
|
||
| let mut attributes = Annotated::new(Attributes::from([ | ||
| ( | ||
| "replace.empty".to_owned(), | ||
| Annotated::new("Should be moved".to_owned().into()), | ||
| ), | ||
| ( | ||
| "replace.existing".to_owned(), | ||
| Annotated::new("Should be removed".to_owned().into()), | ||
| ), | ||
| ( | ||
| "not.replaced".to_owned(), | ||
| Annotated::new("Should be left alone".to_owned().into()), | ||
| ), | ||
| ( | ||
| "backfill.empty".to_owned(), | ||
| Annotated::new("Should be copied".to_owned().into()), | ||
| ), | ||
| ( | ||
| "backfill.existing".to_owned(), | ||
| Annotated::new("Should be left alone".to_owned().into()), | ||
| ), | ||
| ( | ||
| "not.backfilled".to_owned(), | ||
| Annotated::new("Should be left alone".to_owned().into()), | ||
| ), | ||
| ])); | ||
|
|
||
| normalize_attribute_names_inner(&mut attributes, mock_attribute_info); | ||
|
|
||
| insta::assert_json_snapshot!(SerializableAnnotated(&attributes), @r###" | ||
| { | ||
| "backfill.empty": { | ||
| "type": "string", | ||
| "value": "Should be copied" | ||
| }, | ||
| "backfill.existing": { | ||
| "type": "string", | ||
| "value": "Should be left alone" | ||
| }, | ||
| "backfilled": { | ||
| "type": "string", | ||
| "value": "Should be copied" | ||
| }, | ||
| "not.backfilled": { | ||
| "type": "string", | ||
| "value": "Should be left alone" | ||
| }, | ||
| "not.replaced": { | ||
| "type": "string", | ||
| "value": "Should be left alone" | ||
| }, | ||
| "replace.empty": null, | ||
| "replace.existing": null, | ||
| "replaced": { | ||
| "type": "string", | ||
| "value": "Should be moved" | ||
| }, | ||
| "_meta": { | ||
| "replace.empty": { | ||
| "": { | ||
| "rem": [ | ||
| [ | ||
| "attribute.deprecated", | ||
| "x" | ||
| ] | ||
| ] | ||
| } | ||
| }, | ||
| "replace.existing": { | ||
| "": { | ||
| "rem": [ | ||
| [ | ||
| "attribute.deprecated", | ||
| "x" | ||
| ] | ||
| ] | ||
| } | ||
| } | ||
| } | ||
| } | ||
| "###); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -71,10 +71,11 @@ fn normalize_span( | |
| if let Some(span) = span.value_mut() { | ||
| eap::normalize_received(&mut span.attributes, meta.received_at()); | ||
| eap::normalize_user_agent(&mut span.attributes, meta.user_agent(), meta.client_hints()); | ||
| eap::normalize_attribute_types(&mut span.attributes); | ||
| eap::normalize_user_geo(&mut span.attributes, || { | ||
| meta.client_addr().and_then(|ip| geo_lookup.lookup(ip)) | ||
| }); | ||
| eap::normalize_attribute_types(&mut span.attributes); | ||
| eap::normalize_attribute_names(&mut span.attributes); | ||
loewenheim marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // TODO: ai model costs | ||
| } else { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.