From 607922418166f13809608a5541e63f329711d2d0 Mon Sep 17 00:00:00 2001 From: Iker Barriocanal <32816711+iker-barriocanal@users.noreply.github.com> Date: Thu, 16 Nov 2023 12:32:40 +0100 Subject: [PATCH 1/2] ref(normalization): Reduce processor module visibility --- .../src/normalize/mod.rs | 2 +- .../src/normalize/processor.rs | 1448 ++++++++++++++++- .../src/transactions/processor.rs | 1444 ---------------- 3 files changed, 1443 insertions(+), 1451 deletions(-) diff --git a/relay-event-normalization/src/normalize/mod.rs b/relay-event-normalization/src/normalize/mod.rs index e39b822c83d..c4801362d28 100644 --- a/relay-event-normalization/src/normalize/mod.rs +++ b/relay-event-normalization/src/normalize/mod.rs @@ -30,7 +30,6 @@ use crate::{ pub mod breakdowns; pub mod nel; -pub(crate) mod processor; pub mod span; pub mod user_agent; pub mod utils; @@ -38,6 +37,7 @@ pub mod utils; mod contexts; mod logentry; mod mechanism; +mod processor; mod request; mod stacktrace; diff --git a/relay-event-normalization/src/normalize/processor.rs b/relay-event-normalization/src/normalize/processor.rs index 24eea02b844..e023552dac0 100644 --- a/relay-event-normalization/src/normalize/processor.rs +++ b/relay-event-normalization/src/normalize/processor.rs @@ -1005,14 +1005,20 @@ fn normalize_app_start_measurements(measurements: &mut Measurements) { mod tests { use std::collections::BTreeMap; - use chrono::{TimeZone, Utc}; + use chrono::{Duration, TimeZone, Utc}; use insta::assert_debug_snapshot; + use itertools::Itertools; use relay_base_schema::events::EventType; use relay_base_schema::metrics::{DurationUnit, MetricUnit}; - use relay_event_schema::processor::{self, process_value, ProcessingAction, ProcessingState}; + use relay_base_schema::spans::SpanStatus; + use relay_common::glob2::LazyGlob; + use relay_common::time::UnixTimestamp; + use relay_event_schema::processor::{ + self, process_value, ProcessingAction, ProcessingState, Processor, + }; use relay_event_schema::protocol::{ - Contexts, Csp, DeviceContext, Event, Headers, IpAddr, Measurement, Measurements, Request, - Span, SpanId, Tags, TraceContext, TraceId, + ClientSdkInfo, Contexts, Csp, DeviceContext, Event, Headers, IpAddr, Measurement, + Measurements, Request, Span, SpanId, Tags, TraceContext, TraceId, TransactionSource, }; use relay_protocol::{get_value, Annotated, Meta, Object, SerializableAnnotated}; use serde_json::json; @@ -1024,8 +1030,9 @@ mod tests { NormalizeProcessor, NormalizeProcessorConfig, }; use crate::{ - ClientHints, DynamicMeasurementsConfig, MeasurementsConfig, PerformanceScoreConfig, - RawUserAgentInfo, + scrub_identifiers, ClientHints, DynamicMeasurementsConfig, MeasurementsConfig, + PerformanceScoreConfig, RawUserAgentInfo, RedactionRule, TransactionNameConfig, + TransactionNameRule, }; #[test] @@ -2074,4 +2081,1433 @@ mod tests { } "###); } + + fn new_test_event() -> Annotated { + let start = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); + let end = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 10).unwrap(); + Annotated::new(Event { + ty: Annotated::new(EventType::Transaction), + transaction: Annotated::new("/".to_owned()), + start_timestamp: Annotated::new(start.into()), + timestamp: Annotated::new(end.into()), + contexts: { + let mut contexts = Contexts::new(); + contexts.add(TraceContext { + trace_id: Annotated::new(TraceId("4c79f60c11214eb38604f4ae0781bfb2".into())), + span_id: Annotated::new(SpanId("fa90fdead5f74053".into())), + op: Annotated::new("http.server".to_owned()), + ..Default::default() + }); + Annotated::new(contexts) + }, + spans: Annotated::new(vec![Annotated::new(Span { + start_timestamp: Annotated::new(start.into()), + timestamp: Annotated::new(end.into()), + trace_id: Annotated::new(TraceId("4c79f60c11214eb38604f4ae0781bfb2".into())), + span_id: Annotated::new(SpanId("fa90fdead5f74053".into())), + op: Annotated::new("db.statement".to_owned()), + ..Default::default() + })]), + ..Default::default() + }) + } + + #[test] + fn test_skips_non_transaction_events() { + let mut event = Annotated::new(Event::default()); + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root(), + ) + .unwrap(); + assert!(event.value().is_some()); + } + + #[test] + fn test_discards_when_missing_timestamp() { + let mut event = Annotated::new(Event { + ty: Annotated::new(EventType::Transaction), + ..Default::default() + }); + + assert_eq!( + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root() + ), + Err(ProcessingAction::InvalidTransaction( + "timestamp hard-required for transaction events" + )) + ); + } + + #[test] + fn test_discards_when_timestamp_out_of_range() { + let mut event = new_test_event(); + + let processor = &mut NormalizeProcessor::new(NormalizeProcessorConfig { + transaction_range: Some(UnixTimestamp::now()..UnixTimestamp::now()), + ..Default::default() + }); + + assert!(matches!( + process_value(&mut event, processor, ProcessingState::root()), + Err(ProcessingAction::InvalidTransaction( + "timestamp is out of the valid range for metrics" + )) + )); + } + + #[test] + fn test_replace_missing_timestamp() { + let span = Span { + start_timestamp: Annotated::new( + Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 1).unwrap().into(), + ), + trace_id: Annotated::new(TraceId("4c79f60c11214eb38604f4ae0781bfb2".into())), + span_id: Annotated::new(SpanId("fa90fdead5f74053".into())), + ..Default::default() + }; + + let mut event = new_test_event().0.unwrap(); + event.spans = Annotated::new(vec![Annotated::new(span)]); + + NormalizeProcessor::default() + .process_event( + &mut event, + &mut Meta::default(), + &ProcessingState::default(), + ) + .unwrap(); + + let spans = event.spans; + let span = get_value!(spans[0]!); + + assert_eq!(span.timestamp, event.timestamp); + assert_eq!(span.status.value().unwrap(), &SpanStatus::DeadlineExceeded); + } + + #[test] + fn test_discards_when_missing_start_timestamp() { + let mut event = Annotated::new(Event { + ty: Annotated::new(EventType::Transaction), + timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), + ..Default::default() + }); + + assert_eq!( + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root() + ), + Err(ProcessingAction::InvalidTransaction( + "start_timestamp hard-required for transaction events" + )) + ); + } + + #[test] + fn test_discards_on_missing_contexts_map() { + let mut event = Annotated::new(Event { + ty: Annotated::new(EventType::Transaction), + timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), + start_timestamp: Annotated::new( + Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(), + ), + ..Default::default() + }); + + assert_eq!( + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root() + ), + Err(ProcessingAction::InvalidTransaction( + "missing valid trace context" + )) + ); + } + + #[test] + fn test_discards_on_missing_context() { + let mut event = Annotated::new(Event { + ty: Annotated::new(EventType::Transaction), + timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), + start_timestamp: Annotated::new( + Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(), + ), + contexts: Annotated::new(Contexts::new()), + ..Default::default() + }); + + assert_eq!( + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root() + ), + Err(ProcessingAction::InvalidTransaction( + "missing valid trace context" + )) + ); + } + + #[test] + fn test_discards_on_null_context() { + let mut event = Annotated::new(Event { + ty: Annotated::new(EventType::Transaction), + timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), + start_timestamp: Annotated::new( + Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(), + ), + contexts: Annotated::new(Contexts({ + let mut contexts = Object::new(); + contexts.insert("trace".to_owned(), Annotated::empty()); + contexts + })), + ..Default::default() + }); + + assert_eq!( + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root() + ), + Err(ProcessingAction::InvalidTransaction( + "missing valid trace context" + )) + ); + } + + #[test] + fn test_discards_on_missing_trace_id_in_context() { + let mut event = Annotated::new(Event { + ty: Annotated::new(EventType::Transaction), + timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), + start_timestamp: Annotated::new( + Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(), + ), + contexts: { + let mut contexts = Contexts::new(); + contexts.add(TraceContext::default()); + Annotated::new(contexts) + }, + ..Default::default() + }); + + assert_eq!( + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root() + ), + Err(ProcessingAction::InvalidTransaction( + "trace context is missing trace_id" + )) + ); + } + + #[test] + fn test_discards_on_missing_span_id_in_context() { + let mut event = Annotated::new(Event { + ty: Annotated::new(EventType::Transaction), + timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), + start_timestamp: Annotated::new( + Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(), + ), + contexts: { + let mut contexts = Contexts::new(); + contexts.add(TraceContext { + trace_id: Annotated::new(TraceId("4c79f60c11214eb38604f4ae0781bfb2".into())), + ..Default::default() + }); + Annotated::new(contexts) + }, + ..Default::default() + }); + + assert_eq!( + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root() + ), + Err(ProcessingAction::InvalidTransaction( + "trace context is missing span_id" + )) + ); + } + + #[test] + fn test_defaults_missing_op_in_context() { + let start = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); + let end = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 10).unwrap(); + + let mut event = Annotated::new(Event { + ty: Annotated::new(EventType::Transaction), + transaction: Annotated::new("/".to_owned()), + timestamp: Annotated::new(end.into()), + start_timestamp: Annotated::new(start.into()), + contexts: { + let mut contexts = Contexts::new(); + contexts.add(TraceContext { + trace_id: Annotated::new(TraceId("4c79f60c11214eb38604f4ae0781bfb2".into())), + span_id: Annotated::new(SpanId("fa90fdead5f74053".into())), + ..Default::default() + }); + Annotated::new(contexts) + }, + ..Default::default() + }); + + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root(), + ) + .unwrap(); + + let trace_context = get_value!(event.contexts) + .unwrap() + .get::() + .unwrap(); + let trace_op = trace_context.op.value().unwrap(); + assert_eq!(trace_op, "default"); + } + + #[test] + fn test_allows_transaction_event_without_span_list() { + let mut event = Annotated::new(Event { + ty: Annotated::new(EventType::Transaction), + timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), + start_timestamp: Annotated::new( + Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(), + ), + contexts: { + let mut contexts = Contexts::new(); + contexts.add(TraceContext { + trace_id: Annotated::new(TraceId("4c79f60c11214eb38604f4ae0781bfb2".into())), + span_id: Annotated::new(SpanId("fa90fdead5f74053".into())), + op: Annotated::new("http.server".to_owned()), + ..Default::default() + }); + Annotated::new(contexts) + }, + ..Default::default() + }); + + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root(), + ) + .unwrap(); + assert!(event.value().is_some()); + } + + #[test] + fn test_allows_transaction_event_with_empty_span_list() { + let mut event = Annotated::new(Event { + ty: Annotated::new(EventType::Transaction), + timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), + start_timestamp: Annotated::new( + Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(), + ), + contexts: { + let mut contexts = Contexts::new(); + contexts.add(TraceContext { + trace_id: Annotated::new(TraceId("4c79f60c11214eb38604f4ae0781bfb2".into())), + span_id: Annotated::new(SpanId("fa90fdead5f74053".into())), + op: Annotated::new("http.server".to_owned()), + ..Default::default() + }); + Annotated::new(contexts) + }, + spans: Annotated::new(vec![]), + ..Default::default() + }); + + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root(), + ) + .unwrap(); + assert!(event.value().is_some()); + } + + #[test] + fn test_allows_transaction_event_with_null_span_list() { + let mut event = new_test_event(); + + processor::apply(&mut event, |event, _| { + event.spans.set_value(None); + Ok(()) + }) + .unwrap(); + + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root(), + ) + .unwrap(); + assert!(get_value!(event.spans).unwrap().is_empty()); + } + + #[test] + fn test_discards_transaction_event_with_nulled_out_span() { + let mut event = Annotated::new(Event { + ty: Annotated::new(EventType::Transaction), + timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), + start_timestamp: Annotated::new( + Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(), + ), + contexts: { + let mut contexts = Contexts::new(); + contexts.add(TraceContext { + trace_id: Annotated::new(TraceId("4c79f60c11214eb38604f4ae0781bfb2".into())), + span_id: Annotated::new(SpanId("fa90fdead5f74053".into())), + op: Annotated::new("http.server".to_owned()), + ..Default::default() + }); + Annotated::new(contexts) + }, + spans: Annotated::new(vec![Annotated::empty()]), + ..Default::default() + }); + + assert_eq!( + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root() + ), + Err(ProcessingAction::InvalidTransaction( + "spans must be valid in transaction event" + )) + ); + } + + #[test] + fn test_default_transaction_source_unknown() { + let mut event = Annotated::::from_json( + r#" + { + "type": "transaction", + "transaction": "/", + "timestamp": 946684810.0, + "start_timestamp": 946684800.0, + "contexts": { + "trace": { + "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", + "span_id": "fa90fdead5f74053", + "op": "http.server", + "type": "trace" + } + }, + "sdk": { + "name": "sentry.dart.flutter" + }, + "spans": [] + } + "#, + ) + .unwrap(); + + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root(), + ) + .unwrap(); + + let source = event + .value() + .unwrap() + .transaction_info + .value() + .and_then(|info| info.source.value()) + .unwrap(); + + assert_eq!(source, &TransactionSource::Unknown); + } + + #[test] + fn test_allows_valid_transaction_event_with_spans() { + let mut event = new_test_event(); + + assert!(process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root(), + ) + .is_ok()); + } + + #[test] + fn test_defaults_transaction_name_when_missing() { + let mut event = new_test_event(); + + processor::apply(&mut event, |event, _| { + event.transaction.set_value(None); + Ok(()) + }) + .unwrap(); + + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root(), + ) + .unwrap(); + + assert_eq!(get_value!(event.transaction!), ""); + } + + #[test] + fn test_defaults_transaction_name_when_empty() { + let mut event = new_test_event(); + + processor::apply(&mut event, |event, _| { + event.transaction.set_value(Some("".to_owned())); + Ok(()) + }) + .unwrap(); + + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root(), + ) + .unwrap(); + + assert_eq!(get_value!(event.transaction!), ""); + } + + #[test] + fn test_transaction_name_normalize() { + let json = r#" + { + "type": "transaction", + "transaction": "/foo/2fd4e1c67a2d28fced849ee1bb76e7391b93eb12/user/123/0", + "transaction_info": { + "source": "url" + }, + "timestamp": "2021-04-26T08:00:00+0100", + "start_timestamp": "2021-04-26T07:59:01+0100", + "contexts": { + "trace": { + "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", + "span_id": "fa90fdead5f74053", + "op": "rails.request", + "status": "ok" + } + }, + "sdk": {"name": "sentry.ruby"}, + "modules": {"rack": "1.2.3"} + } + "#; + let mut event = Annotated::::from_json(json).unwrap(); + + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root(), + ) + .unwrap(); + + assert_eq!(get_value!(event.transaction!), "/foo/*/user/*/0"); + assert_eq!( + get_value!(event.transaction_info.source!).as_str(), + "sanitized" + ); + + let remarks = get_value!(event!) + .transaction + .meta() + .iter_remarks() + .collect_vec(); + assert_debug_snapshot!(remarks, @r#"[ + Remark { + ty: Substituted, + rule_id: "int", + range: Some( + ( + 5, + 45, + ), + ), + }, + Remark { + ty: Substituted, + rule_id: "int", + range: Some( + ( + 51, + 54, + ), + ), + }, +]"#); + } + + /// When no identifiers are scrubbed, we should not set an original value in _meta. + #[test] + fn test_transaction_name_skip_original_value() { + let json = r#" + { + "type": "transaction", + "transaction": "/foo/static/page", + "transaction_info": { + "source": "url" + }, + "timestamp": "2021-04-26T08:00:00+0100", + "start_timestamp": "2021-04-26T07:59:01+0100", + "contexts": { + "trace": { + "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", + "span_id": "fa90fdead5f74053", + "op": "rails.request", + "status": "ok" + } + }, + "sdk": {"name": "sentry.ruby"}, + "modules": {"rack": "1.2.3"} + } + "#; + let mut event = Annotated::::from_json(json).unwrap(); + + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root(), + ) + .unwrap(); + + assert!(event.meta().is_empty()); + } + + #[test] + fn test_transaction_name_normalize_mark_as_sanitized() { + let json = r#" + { + "type": "transaction", + "transaction": "/foo/2fd4e1c67a2d28fced849ee1bb76e7391b93eb12/user/123/0", + "transaction_info": { + "source": "url" + }, + "timestamp": "2021-04-26T08:00:00+0100", + "start_timestamp": "2021-04-26T07:59:01+0100", + "contexts": { + "trace": { + "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", + "span_id": "fa90fdead5f74053", + "op": "rails.request", + "status": "ok" + } + } + + } + "#; + let mut event = Annotated::::from_json(json).unwrap(); + + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root(), + ) + .unwrap(); + + assert_eq!(get_value!(event.transaction!), "/foo/*/user/*/0"); + assert_eq!( + get_value!(event.transaction_info.source!).as_str(), + "sanitized" + ); + } + + #[test] + fn test_transaction_name_rename_with_rules() { + let json = r#" + { + "type": "transaction", + "transaction": "/foo/rule-target/user/123/0/", + "transaction_info": { + "source": "url" + }, + "timestamp": "2021-04-26T08:00:00+0100", + "start_timestamp": "2021-04-26T07:59:01+0100", + "contexts": { + "trace": { + "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", + "span_id": "fa90fdead5f74053", + "op": "rails.request", + "status": "ok" + } + }, + "sdk": {"name": "sentry.ruby"}, + "modules": {"rack": "1.2.3"} + } + "#; + + let rule1 = TransactionNameRule { + pattern: LazyGlob::new("/foo/*/user/*/**".to_string()), + expiry: Utc::now() + Duration::hours(1), + redaction: Default::default(), + }; + let rule2 = TransactionNameRule { + pattern: LazyGlob::new("/foo/*/**".to_string()), + expiry: Utc::now() + Duration::hours(1), + redaction: Default::default(), + }; + // This should not happend, such rules shouldn't be sent to relay at all. + let rule3 = TransactionNameRule { + pattern: LazyGlob::new("/*/**".to_string()), + expiry: Utc::now() + Duration::hours(1), + redaction: Default::default(), + }; + + let mut event = Annotated::::from_json(json).unwrap(); + + process_value( + &mut event, + &mut NormalizeProcessor::new(NormalizeProcessorConfig { + transaction_name_config: TransactionNameConfig { + rules: &[rule1, rule2, rule3], + }, + ..Default::default() + }), + ProcessingState::root(), + ) + .unwrap(); + + assert_eq!(get_value!(event.transaction!), "/foo/*/user/*/0/"); + assert_eq!( + get_value!(event.transaction_info.source!).as_str(), + "sanitized" + ); + + let remarks = get_value!(event!) + .transaction + .meta() + .iter_remarks() + .collect_vec(); + assert_debug_snapshot!(remarks, @r#"[ + Remark { + ty: Substituted, + rule_id: "int", + range: Some( + ( + 22, + 25, + ), + ), + }, + Remark { + ty: Substituted, + rule_id: "/foo/*/user/*/**", + range: None, + }, +]"#); + } + + #[test] + fn test_transaction_name_rules_skip_expired() { + let json = r#" + { + "type": "transaction", + "transaction": "/foo/rule-target/user/123/0/", + "transaction_info": { + "source": "url" + }, + "timestamp": "2021-04-26T08:00:00+0100", + "start_timestamp": "2021-04-26T07:59:01+0100", + "contexts": { + "trace": { + "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", + "span_id": "fa90fdead5f74053", + "op": "rails.request", + "status": "ok" + } + }, + "sdk": {"name": "sentry.ruby"}, + "modules": {"rack": "1.2.3"} + } + "#; + let mut event = Annotated::::from_json(json).unwrap(); + + let rule1 = TransactionNameRule { + pattern: LazyGlob::new("/foo/*/user/*/**".to_string()), + expiry: Utc::now() - Duration::hours(1), // Expired rule + redaction: Default::default(), + }; + let rule2 = TransactionNameRule { + pattern: LazyGlob::new("/foo/*/**".to_string()), + expiry: Utc::now() + Duration::hours(1), + redaction: Default::default(), + }; + // This should not happend, such rules shouldn't be sent to relay at all. + let rule3 = TransactionNameRule { + pattern: LazyGlob::new("/*/**".to_string()), + expiry: Utc::now() + Duration::hours(1), + redaction: Default::default(), + }; + + process_value( + &mut event, + &mut NormalizeProcessor::new(NormalizeProcessorConfig { + transaction_name_config: TransactionNameConfig { + rules: &[rule1, rule2, rule3], + }, + ..Default::default() + }), + ProcessingState::root(), + ) + .unwrap(); + + assert_eq!(get_value!(event.transaction!), "/foo/*/user/*/0/"); + assert_eq!( + get_value!(event.transaction_info.source!).as_str(), + "sanitized" + ); + + let remarks = get_value!(event!) + .transaction + .meta() + .iter_remarks() + .collect_vec(); + assert_debug_snapshot!(remarks, @r#"[ + Remark { + ty: Substituted, + rule_id: "int", + range: Some( + ( + 22, + 25, + ), + ), + }, + Remark { + ty: Substituted, + rule_id: "/foo/*/**", + range: None, + }, +]"#); + } + + #[test] + fn test_normalize_twice() { + // Simulate going through a chain of relays. + let json = r#" + { + "type": "transaction", + "transaction": "/foo/rule-target/user/123/0/", + "transaction_info": { + "source": "url" + }, + "timestamp": "2021-04-26T08:00:00+0100", + "start_timestamp": "2021-04-26T07:59:01+0100", + "contexts": { + "trace": { + "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", + "span_id": "fa90fdead5f74053", + "op": "rails.request" + } + } + } + "#; + + let rules = vec![TransactionNameRule { + pattern: LazyGlob::new("/foo/*/user/*/**".to_string()), + expiry: Utc::now() + Duration::hours(1), + redaction: Default::default(), + }]; + + let mut event = Annotated::::from_json(json).unwrap(); + + let mut processor = NormalizeProcessor::new(NormalizeProcessorConfig { + transaction_name_config: TransactionNameConfig { rules: &rules }, + ..Default::default() + }); + process_value(&mut event, &mut processor, ProcessingState::root()).unwrap(); + + assert_eq!(get_value!(event.transaction!), "/foo/*/user/*/0/"); + assert_eq!( + get_value!(event.transaction_info.source!).as_str(), + "sanitized" + ); + + let remarks = get_value!(event!) + .transaction + .meta() + .iter_remarks() + .collect_vec(); + assert_debug_snapshot!(remarks, @r#"[ + Remark { + ty: Substituted, + rule_id: "int", + range: Some( + ( + 22, + 25, + ), + ), + }, + Remark { + ty: Substituted, + rule_id: "/foo/*/user/*/**", + range: None, + }, +]"#); + + assert_eq!( + get_value!(event.transaction_info.source!).as_str(), + "sanitized" + ); + + // Process again: + process_value(&mut event, &mut processor, ProcessingState::root()).unwrap(); + + assert_eq!(get_value!(event.transaction!), "/foo/*/user/*/0/"); + assert_eq!( + get_value!(event.transaction_info.source!).as_str(), + "sanitized" + ); + + let remarks = get_value!(event!) + .transaction + .meta() + .iter_remarks() + .collect_vec(); + assert_debug_snapshot!(remarks, @r#"[ + Remark { + ty: Substituted, + rule_id: "int", + range: Some( + ( + 22, + 25, + ), + ), + }, + Remark { + ty: Substituted, + rule_id: "/foo/*/user/*/**", + range: None, + }, +]"#); + + assert_eq!( + get_value!(event.transaction_info.source!).as_str(), + "sanitized" + ); + } + + #[test] + fn test_transaction_name_unsupported_source() { + let json = r#" + { + "type": "transaction", + "transaction": "/foo/2fd4e1c67a2d28fced849ee1bb76e7391b93eb12/user/123/0", + "transaction_info": { + "source": "foobar" + }, + "timestamp": "2021-04-26T08:00:00+0100", + "start_timestamp": "2021-04-26T07:59:01+0100", + "contexts": { + "trace": { + "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", + "span_id": "fa90fdead5f74053", + "op": "rails.request", + "status": "ok" + } + } + } + "#; + let mut event = Annotated::::from_json(json).unwrap(); + let rule1 = TransactionNameRule { + pattern: LazyGlob::new("/foo/*/**".to_string()), + expiry: Utc::now() + Duration::hours(1), + redaction: Default::default(), + }; + // This should not happend, such rules shouldn't be sent to relay at all. + let rule2 = TransactionNameRule { + pattern: LazyGlob::new("/*/**".to_string()), + expiry: Utc::now() + Duration::hours(1), + redaction: Default::default(), + }; + let rules = vec![rule1, rule2]; + + // This must not normalize transaction name, since it's disabled. + process_value( + &mut event, + &mut NormalizeProcessor::new(NormalizeProcessorConfig { + transaction_name_config: TransactionNameConfig { rules: &rules }, + ..Default::default() + }), + ProcessingState::root(), + ) + .unwrap(); + + assert_eq!( + get_value!(event.transaction!), + "/foo/2fd4e1c67a2d28fced849ee1bb76e7391b93eb12/user/123/0" + ); + assert!(get_value!(event!) + .transaction + .meta() + .iter_remarks() + .next() + .is_none()); + assert_eq!( + get_value!(event.transaction_info.source!).as_str(), + "foobar" + ); + } + + fn run_with_unknown_source(sdk: &str) -> Annotated { + let json = r#" + { + "type": "transaction", + "transaction": "/user/jane/blog/", + "timestamp": "2021-04-26T08:00:00+0100", + "start_timestamp": "2021-04-26T07:59:01+0100", + "contexts": { + "trace": { + "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", + "span_id": "fa90fdead5f74053", + "op": "rails.request", + "status": "ok" + } + } + } + "#; + let mut event = Annotated::::from_json(json).unwrap(); + event + .value_mut() + .as_mut() + .unwrap() + .client_sdk + .set_value(Some(ClientSdkInfo { + name: sdk.to_owned().into(), + ..Default::default() + })); + let rules: Vec = serde_json::from_value(serde_json::json!([ + {"pattern": "/user/*/**", "expiry": "3021-04-26T07:59:01+0100", "redaction": {"method": "replace"}} + ])) + .unwrap(); + + process_value( + &mut event, + &mut NormalizeProcessor::new(NormalizeProcessorConfig { + transaction_name_config: TransactionNameConfig { rules: &rules }, + ..Default::default() + }), + ProcessingState::root(), + ) + .unwrap(); + event + } + + #[test] + fn test_normalize_legacy_javascript() { + // Javascript without source annotation gets sanitized. + let event = run_with_unknown_source("sentry.javascript.browser"); + + assert_eq!(get_value!(event.transaction!), "/user/*/blog/"); + assert_eq!( + get_value!(event.transaction_info.source!).as_str(), + "sanitized" + ); + + let remarks = get_value!(event!) + .transaction + .meta() + .iter_remarks() + .collect_vec(); + assert_debug_snapshot!(remarks, @r#"[ + Remark { + ty: Substituted, + rule_id: "/user/*/**", + range: None, + }, +]"#); + + assert_eq!( + get_value!(event.transaction_info.source!).as_str(), + "sanitized" + ); + } + + #[test] + fn test_normalize_legacy_python() { + // Python without source annotation does not get sanitized, because we assume it to be + // low cardinality. + let event = run_with_unknown_source("sentry.python"); + assert_eq!(get_value!(event.transaction!), "/user/jane/blog/"); + assert_eq!( + get_value!(event.transaction_info.source!).as_str(), + "unknown" + ); + } + + #[test] + fn test_transaction_name_rename_end_slash() { + let json = r#" + { + "type": "transaction", + "transaction": "/foo/rule-target/user", + "transaction_info": { + "source": "url" + }, + "timestamp": "2021-04-26T08:00:00+0100", + "start_timestamp": "2021-04-26T07:59:01+0100", + "contexts": { + "trace": { + "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", + "span_id": "fa90fdead5f74053", + "op": "rails.request", + "status": "ok" + } + }, + "sdk": {"name": "sentry.ruby"}, + "modules": {"rack": "1.2.3"} + } + "#; + + let rule = TransactionNameRule { + pattern: LazyGlob::new("/foo/*/**".to_string()), + expiry: Utc::now() + Duration::hours(1), + redaction: Default::default(), + }; + + let mut event = Annotated::::from_json(json).unwrap(); + + process_value( + &mut event, + &mut NormalizeProcessor::new(NormalizeProcessorConfig { + transaction_name_config: TransactionNameConfig { rules: &[rule] }, + ..Default::default() + }), + ProcessingState::root(), + ) + .unwrap(); + + assert_eq!(get_value!(event.transaction!), "/foo/*/user"); + assert_eq!( + get_value!(event.transaction_info.source!).as_str(), + "sanitized" + ); + + let remarks = get_value!(event!) + .transaction + .meta() + .iter_remarks() + .collect_vec(); + assert_debug_snapshot!(remarks, @r#"[ + Remark { + ty: Substituted, + rule_id: "/foo/*/**", + range: None, + }, +]"#); + + assert_eq!( + get_value!(event.transaction_info.source!).as_str(), + "sanitized" + ); + } + + #[test] + fn test_normalize_transaction_names() { + let should_be_replaced = [ + "/aaa11111-aa11-11a1-a11a-1aaa1111a111", + "/1aa111aa-11a1-11aa-a111-a1a11111aa11", + "/00a00000-0000-0000-0000-000000000001", + "/test/b25feeaa-ed2d-4132-bcbd-6232b7922add/url", + ]; + let replaced = should_be_replaced.map(|s| { + let mut s = Annotated::new(s.to_owned()); + scrub_identifiers(&mut s).unwrap(); + s.0.unwrap() + }); + assert_eq!( + replaced, + ["/*", "/*", "/*", "/test/*/url",].map(str::to_owned) + ) + } + + macro_rules! transaction_name_test { + ($name:ident, $input:literal, $output:literal) => { + #[test] + fn $name() { + let json = format!( + r#" + {{ + "type": "transaction", + "transaction": "{}", + "transaction_info": {{ + "source": "url" + }}, + "timestamp": "2021-04-26T08:00:00+0100", + "start_timestamp": "2021-04-26T07:59:01+0100", + "contexts": {{ + "trace": {{ + "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", + "span_id": "fa90fdead5f74053", + "op": "rails.request", + "status": "ok" + }} + }} + }} + "#, + $input + ); + + let mut event = Annotated::::from_json(&json).unwrap(); + + process_value( + &mut event, + &mut NormalizeProcessor::default(), + ProcessingState::root(), + ) + .unwrap(); + + assert_eq!($output, event.value().unwrap().transaction.value().unwrap()); + } + }; + } + + transaction_name_test!(test_transaction_name_normalize_id, "/1234", "/*"); + transaction_name_test!( + test_transaction_name_normalize_in_segments_1, + "/user/path-with-1234/", + "/user/*/" + ); + transaction_name_test!( + test_transaction_name_normalize_in_segments_2, + "/testing/open-19-close/1", + "/testing/*/1" + ); + transaction_name_test!( + test_transaction_name_normalize_in_segments_3, + "/testing/open19close/1", + "/testing/*/1" + ); + transaction_name_test!( + test_transaction_name_normalize_in_segments_4, + "/testing/asdf012/asdf034/asdf056", + "/testing/*/*/*" + ); + transaction_name_test!( + test_transaction_name_normalize_in_segments_5, + "/foo/test%A33/1234", + "/foo/test%A33/*" + ); + transaction_name_test!( + test_transaction_name_normalize_url_encode_1, + "/%2Ftest%2Fopen%20and%20help%2F1%0A", + "/%2Ftest%2Fopen%20and%20help%2F1%0A" + ); + transaction_name_test!( + test_transaction_name_normalize_url_encode_2, + "/this/1234/%E2%9C%85/foo/bar/098123908213", + "/this/*/%E2%9C%85/foo/bar/*" + ); + transaction_name_test!( + test_transaction_name_normalize_url_encode_3, + "/foo/hello%20world-4711/", + "/foo/*/" + ); + transaction_name_test!( + test_transaction_name_normalize_url_encode_4, + "/foo/hello%20world-0xdeadbeef/", + "/foo/*/" + ); + transaction_name_test!( + test_transaction_name_normalize_url_encode_5, + "/foo/hello%20world-4711/", + "/foo/*/" + ); + transaction_name_test!( + test_transaction_name_normalize_url_encode_6, + "/foo/hello%2Fworld/", + "/foo/hello%2Fworld/" + ); + transaction_name_test!( + test_transaction_name_normalize_url_encode_7, + "/foo/hello%201/", + "/foo/hello%201/" + ); + transaction_name_test!( + test_transaction_name_normalize_sha, + "/hash/4c79f60c11214eb38604f4ae0781bfb2/diff", + "/hash/*/diff" + ); + transaction_name_test!( + test_transaction_name_normalize_uuid, + "/u/7b25feea-ed2d-4132-bcbd-6232b7922add/edit", + "/u/*/edit" + ); + transaction_name_test!( + test_transaction_name_normalize_hex, + "/u/0x3707344A4093822299F31D008/profile/123123213", + "/u/*/profile/*" + ); + transaction_name_test!( + test_transaction_name_normalize_windows_path, + r"C:\\\\Program Files\\1234\\Files", + r"C:\\Program Files\*\Files" + ); + transaction_name_test!(test_transaction_name_skip_replace_all, "12345", "12345"); + transaction_name_test!( + test_transaction_name_skip_replace_all2, + "open-12345-close", + "open-12345-close" + ); + + #[test] + fn test_scrub_identifiers_before_rules() { + // There's a rule matching the transaction name. However, the UUID + // should be scrubbed first. Scrubbing the UUID makes the rule to not + // match the transformed transaction name anymore. + + let mut event = Annotated::::from_json( + r#"{ + "type": "transaction", + "transaction": "/remains/rule-target/1234567890", + "transaction_info": { + "source": "url" + }, + "timestamp": "2021-04-26T08:00:00+0100", + "start_timestamp": "2021-04-26T07:59:01+0100", + "contexts": { + "trace": { + "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", + "span_id": "fa90fdead5f74053" + } + } + }"#, + ) + .unwrap(); + + process_value( + &mut event, + &mut NormalizeProcessor::new(NormalizeProcessorConfig { + transaction_name_config: TransactionNameConfig { + rules: &[TransactionNameRule { + pattern: LazyGlob::new("/remains/*/1234567890/".to_owned()), + expiry: Utc.with_ymd_and_hms(3000, 1, 1, 1, 1, 1).unwrap(), + redaction: RedactionRule::default(), + }], + }, + ..Default::default() + }), + ProcessingState::root(), + ) + .unwrap(); + + assert_eq!(get_value!(event.transaction!), "/remains/rule-target/*"); + assert_eq!( + get_value!(event.transaction_info.source!).as_str(), + "sanitized" + ); + + let remarks = get_value!(event!) + .transaction + .meta() + .iter_remarks() + .collect_vec(); + assert_debug_snapshot!(remarks, @r#"[ + Remark { + ty: Substituted, + rule_id: "int", + range: Some( + ( + 21, + 31, + ), + ), + }, +]"#); + assert_eq!( + get_value!(event.transaction_info.source!).as_str(), + "sanitized" + ); + } + + #[test] + fn test_scrub_identifiers_and_apply_rules() { + // Ensure rules are applied after scrubbing identifiers. Rules are only + // applied when `transaction.source="url"`, so this test ensures this + // value isn't set as part of identifier scrubbing. + let mut event = Annotated::::from_json( + r#"{ + "type": "transaction", + "transaction": "/remains/rule-target/1234567890", + "transaction_info": { + "source": "url" + }, + "timestamp": "2021-04-26T08:00:00+0100", + "start_timestamp": "2021-04-26T07:59:01+0100", + "contexts": { + "trace": { + "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", + "span_id": "fa90fdead5f74053" + } + } + }"#, + ) + .unwrap(); + + process_value( + &mut event, + &mut NormalizeProcessor::new(NormalizeProcessorConfig { + transaction_name_config: TransactionNameConfig { + rules: &[TransactionNameRule { + pattern: LazyGlob::new("/remains/*/**".to_owned()), + expiry: Utc.with_ymd_and_hms(3000, 1, 1, 1, 1, 1).unwrap(), + redaction: RedactionRule::default(), + }], + }, + ..Default::default() + }), + ProcessingState::root(), + ) + .unwrap(); + + assert_eq!(get_value!(event.transaction!), "/remains/*/*"); + assert_eq!( + get_value!(event.transaction_info.source!).as_str(), + "sanitized" + ); + + let remarks = get_value!(event!) + .transaction + .meta() + .iter_remarks() + .collect_vec(); + assert_debug_snapshot!(remarks, @r#"[ + Remark { + ty: Substituted, + rule_id: "int", + range: Some( + ( + 21, + 31, + ), + ), + }, + Remark { + ty: Substituted, + rule_id: "/remains/*/**", + range: None, + }, +]"#); + } } diff --git a/relay-event-normalization/src/transactions/processor.rs b/relay-event-normalization/src/transactions/processor.rs index 32710a4b2cc..c8eb92b6781 100644 --- a/relay-event-normalization/src/transactions/processor.rs +++ b/relay-event-normalization/src/transactions/processor.rs @@ -378,532 +378,9 @@ fn apply_transaction_rename_rule( #[cfg(test)] mod tests { - use chrono::offset::TimeZone; - use chrono::{Duration, Utc}; - use insta::assert_debug_snapshot; - use itertools::Itertools; - use relay_base_schema::events::EventType; - use relay_common::glob2::LazyGlob; - use relay_event_schema::processor::{process_value, ProcessingState, Processor}; - use relay_event_schema::protocol::{ - ClientSdkInfo, Contexts, Span, SpanId, TraceId, TransactionSource, - }; - use relay_protocol::{get_value, Meta, Object}; - use similar_asserts::assert_eq; use super::*; - use crate::processor::{NormalizeProcessor, NormalizeProcessorConfig}; - use crate::RedactionRule; - - fn new_test_event() -> Annotated { - let start = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); - let end = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 10).unwrap(); - Annotated::new(Event { - ty: Annotated::new(EventType::Transaction), - transaction: Annotated::new("/".to_owned()), - start_timestamp: Annotated::new(start.into()), - timestamp: Annotated::new(end.into()), - contexts: { - let mut contexts = Contexts::new(); - contexts.add(TraceContext { - trace_id: Annotated::new(TraceId("4c79f60c11214eb38604f4ae0781bfb2".into())), - span_id: Annotated::new(SpanId("fa90fdead5f74053".into())), - op: Annotated::new("http.server".to_owned()), - ..Default::default() - }); - Annotated::new(contexts) - }, - spans: Annotated::new(vec![Annotated::new(Span { - start_timestamp: Annotated::new(start.into()), - timestamp: Annotated::new(end.into()), - trace_id: Annotated::new(TraceId("4c79f60c11214eb38604f4ae0781bfb2".into())), - span_id: Annotated::new(SpanId("fa90fdead5f74053".into())), - op: Annotated::new("db.statement".to_owned()), - ..Default::default() - })]), - ..Default::default() - }) - } - - #[test] - fn test_skips_non_transaction_events() { - let mut event = Annotated::new(Event::default()); - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root(), - ) - .unwrap(); - assert!(event.value().is_some()); - } - - #[test] - fn test_discards_when_missing_timestamp() { - let mut event = Annotated::new(Event { - ty: Annotated::new(EventType::Transaction), - ..Default::default() - }); - - assert_eq!( - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root() - ), - Err(ProcessingAction::InvalidTransaction( - "timestamp hard-required for transaction events" - )) - ); - } - - #[test] - fn test_discards_when_timestamp_out_of_range() { - let mut event = new_test_event(); - - let processor = &mut NormalizeProcessor::new(NormalizeProcessorConfig { - transaction_range: Some(UnixTimestamp::now()..UnixTimestamp::now()), - ..Default::default() - }); - - assert!(matches!( - process_value(&mut event, processor, ProcessingState::root()), - Err(ProcessingAction::InvalidTransaction( - "timestamp is out of the valid range for metrics" - )) - )); - } - - #[test] - fn test_replace_missing_timestamp() { - let span = Span { - start_timestamp: Annotated::new( - Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 1).unwrap().into(), - ), - trace_id: Annotated::new(TraceId("4c79f60c11214eb38604f4ae0781bfb2".into())), - span_id: Annotated::new(SpanId("fa90fdead5f74053".into())), - ..Default::default() - }; - - let mut event = new_test_event().0.unwrap(); - event.spans = Annotated::new(vec![Annotated::new(span)]); - - NormalizeProcessor::default() - .process_event( - &mut event, - &mut Meta::default(), - &ProcessingState::default(), - ) - .unwrap(); - - let spans = event.spans; - let span = get_value!(spans[0]!); - - assert_eq!(span.timestamp, event.timestamp); - assert_eq!(span.status.value().unwrap(), &SpanStatus::DeadlineExceeded); - } - - #[test] - fn test_discards_when_missing_start_timestamp() { - let mut event = Annotated::new(Event { - ty: Annotated::new(EventType::Transaction), - timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), - ..Default::default() - }); - - assert_eq!( - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root() - ), - Err(ProcessingAction::InvalidTransaction( - "start_timestamp hard-required for transaction events" - )) - ); - } - - #[test] - fn test_discards_on_missing_contexts_map() { - let mut event = Annotated::new(Event { - ty: Annotated::new(EventType::Transaction), - timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), - start_timestamp: Annotated::new( - Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(), - ), - ..Default::default() - }); - - assert_eq!( - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root() - ), - Err(ProcessingAction::InvalidTransaction( - "missing valid trace context" - )) - ); - } - - #[test] - fn test_discards_on_missing_context() { - let mut event = Annotated::new(Event { - ty: Annotated::new(EventType::Transaction), - timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), - start_timestamp: Annotated::new( - Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(), - ), - contexts: Annotated::new(Contexts::new()), - ..Default::default() - }); - - assert_eq!( - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root() - ), - Err(ProcessingAction::InvalidTransaction( - "missing valid trace context" - )) - ); - } - - #[test] - fn test_discards_on_null_context() { - let mut event = Annotated::new(Event { - ty: Annotated::new(EventType::Transaction), - timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), - start_timestamp: Annotated::new( - Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(), - ), - contexts: Annotated::new(Contexts({ - let mut contexts = Object::new(); - contexts.insert("trace".to_owned(), Annotated::empty()); - contexts - })), - ..Default::default() - }); - - assert_eq!( - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root() - ), - Err(ProcessingAction::InvalidTransaction( - "missing valid trace context" - )) - ); - } - - #[test] - fn test_discards_on_missing_trace_id_in_context() { - let mut event = Annotated::new(Event { - ty: Annotated::new(EventType::Transaction), - timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), - start_timestamp: Annotated::new( - Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(), - ), - contexts: { - let mut contexts = Contexts::new(); - contexts.add(TraceContext::default()); - Annotated::new(contexts) - }, - ..Default::default() - }); - - assert_eq!( - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root() - ), - Err(ProcessingAction::InvalidTransaction( - "trace context is missing trace_id" - )) - ); - } - - #[test] - fn test_discards_on_missing_span_id_in_context() { - let mut event = Annotated::new(Event { - ty: Annotated::new(EventType::Transaction), - timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), - start_timestamp: Annotated::new( - Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(), - ), - contexts: { - let mut contexts = Contexts::new(); - contexts.add(TraceContext { - trace_id: Annotated::new(TraceId("4c79f60c11214eb38604f4ae0781bfb2".into())), - ..Default::default() - }); - Annotated::new(contexts) - }, - ..Default::default() - }); - - assert_eq!( - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root() - ), - Err(ProcessingAction::InvalidTransaction( - "trace context is missing span_id" - )) - ); - } - - #[test] - fn test_defaults_missing_op_in_context() { - let start = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); - let end = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 10).unwrap(); - - let mut event = Annotated::new(Event { - ty: Annotated::new(EventType::Transaction), - transaction: Annotated::new("/".to_owned()), - timestamp: Annotated::new(end.into()), - start_timestamp: Annotated::new(start.into()), - contexts: { - let mut contexts = Contexts::new(); - contexts.add(TraceContext { - trace_id: Annotated::new(TraceId("4c79f60c11214eb38604f4ae0781bfb2".into())), - span_id: Annotated::new(SpanId("fa90fdead5f74053".into())), - ..Default::default() - }); - Annotated::new(contexts) - }, - ..Default::default() - }); - - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root(), - ) - .unwrap(); - - let trace_context = get_value!(event.contexts) - .unwrap() - .get::() - .unwrap(); - let trace_op = trace_context.op.value().unwrap(); - assert_eq!(trace_op, "default"); - } - - #[test] - fn test_allows_transaction_event_without_span_list() { - let mut event = Annotated::new(Event { - ty: Annotated::new(EventType::Transaction), - timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), - start_timestamp: Annotated::new( - Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(), - ), - contexts: { - let mut contexts = Contexts::new(); - contexts.add(TraceContext { - trace_id: Annotated::new(TraceId("4c79f60c11214eb38604f4ae0781bfb2".into())), - span_id: Annotated::new(SpanId("fa90fdead5f74053".into())), - op: Annotated::new("http.server".to_owned()), - ..Default::default() - }); - Annotated::new(contexts) - }, - ..Default::default() - }); - - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root(), - ) - .unwrap(); - assert!(event.value().is_some()); - } - - #[test] - fn test_allows_transaction_event_with_empty_span_list() { - let mut event = Annotated::new(Event { - ty: Annotated::new(EventType::Transaction), - timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), - start_timestamp: Annotated::new( - Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(), - ), - contexts: { - let mut contexts = Contexts::new(); - contexts.add(TraceContext { - trace_id: Annotated::new(TraceId("4c79f60c11214eb38604f4ae0781bfb2".into())), - span_id: Annotated::new(SpanId("fa90fdead5f74053".into())), - op: Annotated::new("http.server".to_owned()), - ..Default::default() - }); - Annotated::new(contexts) - }, - spans: Annotated::new(vec![]), - ..Default::default() - }); - - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root(), - ) - .unwrap(); - assert!(event.value().is_some()); - } - - #[test] - fn test_allows_transaction_event_with_null_span_list() { - let mut event = new_test_event(); - - processor::apply(&mut event, |event, _| { - event.spans.set_value(None); - Ok(()) - }) - .unwrap(); - - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root(), - ) - .unwrap(); - assert!(get_value!(event.spans).unwrap().is_empty()); - } - - #[test] - fn test_discards_transaction_event_with_nulled_out_span() { - let mut event = Annotated::new(Event { - ty: Annotated::new(EventType::Transaction), - timestamp: Annotated::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into()), - start_timestamp: Annotated::new( - Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().into(), - ), - contexts: { - let mut contexts = Contexts::new(); - contexts.add(TraceContext { - trace_id: Annotated::new(TraceId("4c79f60c11214eb38604f4ae0781bfb2".into())), - span_id: Annotated::new(SpanId("fa90fdead5f74053".into())), - op: Annotated::new("http.server".to_owned()), - ..Default::default() - }); - Annotated::new(contexts) - }, - spans: Annotated::new(vec![Annotated::empty()]), - ..Default::default() - }); - - assert_eq!( - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root() - ), - Err(ProcessingAction::InvalidTransaction( - "spans must be valid in transaction event" - )) - ); - } - - #[test] - fn test_default_transaction_source_unknown() { - let mut event = Annotated::::from_json( - r#" - { - "type": "transaction", - "transaction": "/", - "timestamp": 946684810.0, - "start_timestamp": 946684800.0, - "contexts": { - "trace": { - "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", - "span_id": "fa90fdead5f74053", - "op": "http.server", - "type": "trace" - } - }, - "sdk": { - "name": "sentry.dart.flutter" - }, - "spans": [] - } - "#, - ) - .unwrap(); - - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root(), - ) - .unwrap(); - - let source = event - .value() - .unwrap() - .transaction_info - .value() - .and_then(|info| info.source.value()) - .unwrap(); - - assert_eq!(source, &TransactionSource::Unknown); - } - - #[test] - fn test_allows_valid_transaction_event_with_spans() { - let mut event = new_test_event(); - - assert!(process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root(), - ) - .is_ok()); - } - - #[test] - fn test_defaults_transaction_name_when_missing() { - let mut event = new_test_event(); - - processor::apply(&mut event, |event, _| { - event.transaction.set_value(None); - Ok(()) - }) - .unwrap(); - - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root(), - ) - .unwrap(); - - assert_eq!(get_value!(event.transaction!), ""); - } - - #[test] - fn test_defaults_transaction_name_when_empty() { - let mut event = new_test_event(); - - processor::apply(&mut event, |event, _| { - event.transaction.set_value(Some("".to_owned())); - Ok(()) - }) - .unwrap(); - - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root(), - ) - .unwrap(); - - assert_eq!(get_value!(event.transaction!), ""); - } - #[test] fn test_is_high_cardinality_sdk_ruby_ok() { let json = r#" @@ -950,925 +427,4 @@ mod tests { assert!(is_high_cardinality_sdk(&event.0.unwrap())); } - - #[test] - fn test_transaction_name_normalize() { - let json = r#" - { - "type": "transaction", - "transaction": "/foo/2fd4e1c67a2d28fced849ee1bb76e7391b93eb12/user/123/0", - "transaction_info": { - "source": "url" - }, - "timestamp": "2021-04-26T08:00:00+0100", - "start_timestamp": "2021-04-26T07:59:01+0100", - "contexts": { - "trace": { - "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", - "span_id": "fa90fdead5f74053", - "op": "rails.request", - "status": "ok" - } - }, - "sdk": {"name": "sentry.ruby"}, - "modules": {"rack": "1.2.3"} - } - "#; - let mut event = Annotated::::from_json(json).unwrap(); - - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root(), - ) - .unwrap(); - - assert_eq!(get_value!(event.transaction!), "/foo/*/user/*/0"); - assert_eq!( - get_value!(event.transaction_info.source!).as_str(), - "sanitized" - ); - - let remarks = get_value!(event!) - .transaction - .meta() - .iter_remarks() - .collect_vec(); - assert_debug_snapshot!(remarks, @r#"[ - Remark { - ty: Substituted, - rule_id: "int", - range: Some( - ( - 5, - 45, - ), - ), - }, - Remark { - ty: Substituted, - rule_id: "int", - range: Some( - ( - 51, - 54, - ), - ), - }, -]"#); - } - - /// When no identifiers are scrubbed, we should not set an original value in _meta. - #[test] - fn test_transaction_name_skip_original_value() { - let json = r#" - { - "type": "transaction", - "transaction": "/foo/static/page", - "transaction_info": { - "source": "url" - }, - "timestamp": "2021-04-26T08:00:00+0100", - "start_timestamp": "2021-04-26T07:59:01+0100", - "contexts": { - "trace": { - "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", - "span_id": "fa90fdead5f74053", - "op": "rails.request", - "status": "ok" - } - }, - "sdk": {"name": "sentry.ruby"}, - "modules": {"rack": "1.2.3"} - } - "#; - let mut event = Annotated::::from_json(json).unwrap(); - - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root(), - ) - .unwrap(); - - assert!(event.meta().is_empty()); - } - - #[test] - fn test_transaction_name_normalize_mark_as_sanitized() { - let json = r#" - { - "type": "transaction", - "transaction": "/foo/2fd4e1c67a2d28fced849ee1bb76e7391b93eb12/user/123/0", - "transaction_info": { - "source": "url" - }, - "timestamp": "2021-04-26T08:00:00+0100", - "start_timestamp": "2021-04-26T07:59:01+0100", - "contexts": { - "trace": { - "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", - "span_id": "fa90fdead5f74053", - "op": "rails.request", - "status": "ok" - } - } - - } - "#; - let mut event = Annotated::::from_json(json).unwrap(); - - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root(), - ) - .unwrap(); - - assert_eq!(get_value!(event.transaction!), "/foo/*/user/*/0"); - assert_eq!( - get_value!(event.transaction_info.source!).as_str(), - "sanitized" - ); - } - - #[test] - fn test_transaction_name_rename_with_rules() { - let json = r#" - { - "type": "transaction", - "transaction": "/foo/rule-target/user/123/0/", - "transaction_info": { - "source": "url" - }, - "timestamp": "2021-04-26T08:00:00+0100", - "start_timestamp": "2021-04-26T07:59:01+0100", - "contexts": { - "trace": { - "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", - "span_id": "fa90fdead5f74053", - "op": "rails.request", - "status": "ok" - } - }, - "sdk": {"name": "sentry.ruby"}, - "modules": {"rack": "1.2.3"} - } - "#; - - let rule1 = TransactionNameRule { - pattern: LazyGlob::new("/foo/*/user/*/**".to_string()), - expiry: Utc::now() + Duration::hours(1), - redaction: Default::default(), - }; - let rule2 = TransactionNameRule { - pattern: LazyGlob::new("/foo/*/**".to_string()), - expiry: Utc::now() + Duration::hours(1), - redaction: Default::default(), - }; - // This should not happend, such rules shouldn't be sent to relay at all. - let rule3 = TransactionNameRule { - pattern: LazyGlob::new("/*/**".to_string()), - expiry: Utc::now() + Duration::hours(1), - redaction: Default::default(), - }; - - let mut event = Annotated::::from_json(json).unwrap(); - - process_value( - &mut event, - &mut NormalizeProcessor::new(NormalizeProcessorConfig { - transaction_name_config: TransactionNameConfig { - rules: &[rule1, rule2, rule3], - }, - ..Default::default() - }), - ProcessingState::root(), - ) - .unwrap(); - - assert_eq!(get_value!(event.transaction!), "/foo/*/user/*/0/"); - assert_eq!( - get_value!(event.transaction_info.source!).as_str(), - "sanitized" - ); - - let remarks = get_value!(event!) - .transaction - .meta() - .iter_remarks() - .collect_vec(); - assert_debug_snapshot!(remarks, @r#"[ - Remark { - ty: Substituted, - rule_id: "int", - range: Some( - ( - 22, - 25, - ), - ), - }, - Remark { - ty: Substituted, - rule_id: "/foo/*/user/*/**", - range: None, - }, -]"#); - } - - #[test] - fn test_transaction_name_rules_skip_expired() { - let json = r#" - { - "type": "transaction", - "transaction": "/foo/rule-target/user/123/0/", - "transaction_info": { - "source": "url" - }, - "timestamp": "2021-04-26T08:00:00+0100", - "start_timestamp": "2021-04-26T07:59:01+0100", - "contexts": { - "trace": { - "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", - "span_id": "fa90fdead5f74053", - "op": "rails.request", - "status": "ok" - } - }, - "sdk": {"name": "sentry.ruby"}, - "modules": {"rack": "1.2.3"} - } - "#; - let mut event = Annotated::::from_json(json).unwrap(); - - let rule1 = TransactionNameRule { - pattern: LazyGlob::new("/foo/*/user/*/**".to_string()), - expiry: Utc::now() - Duration::hours(1), // Expired rule - redaction: Default::default(), - }; - let rule2 = TransactionNameRule { - pattern: LazyGlob::new("/foo/*/**".to_string()), - expiry: Utc::now() + Duration::hours(1), - redaction: Default::default(), - }; - // This should not happend, such rules shouldn't be sent to relay at all. - let rule3 = TransactionNameRule { - pattern: LazyGlob::new("/*/**".to_string()), - expiry: Utc::now() + Duration::hours(1), - redaction: Default::default(), - }; - - process_value( - &mut event, - &mut NormalizeProcessor::new(NormalizeProcessorConfig { - transaction_name_config: TransactionNameConfig { - rules: &[rule1, rule2, rule3], - }, - ..Default::default() - }), - ProcessingState::root(), - ) - .unwrap(); - - assert_eq!(get_value!(event.transaction!), "/foo/*/user/*/0/"); - assert_eq!( - get_value!(event.transaction_info.source!).as_str(), - "sanitized" - ); - - let remarks = get_value!(event!) - .transaction - .meta() - .iter_remarks() - .collect_vec(); - assert_debug_snapshot!(remarks, @r#"[ - Remark { - ty: Substituted, - rule_id: "int", - range: Some( - ( - 22, - 25, - ), - ), - }, - Remark { - ty: Substituted, - rule_id: "/foo/*/**", - range: None, - }, -]"#); - } - - #[test] - fn test_normalize_twice() { - // Simulate going through a chain of relays. - let json = r#" - { - "type": "transaction", - "transaction": "/foo/rule-target/user/123/0/", - "transaction_info": { - "source": "url" - }, - "timestamp": "2021-04-26T08:00:00+0100", - "start_timestamp": "2021-04-26T07:59:01+0100", - "contexts": { - "trace": { - "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", - "span_id": "fa90fdead5f74053", - "op": "rails.request" - } - } - } - "#; - - let rules = vec![TransactionNameRule { - pattern: LazyGlob::new("/foo/*/user/*/**".to_string()), - expiry: Utc::now() + Duration::hours(1), - redaction: Default::default(), - }]; - - let mut event = Annotated::::from_json(json).unwrap(); - - let mut processor = NormalizeProcessor::new(NormalizeProcessorConfig { - transaction_name_config: TransactionNameConfig { rules: &rules }, - ..Default::default() - }); - process_value(&mut event, &mut processor, ProcessingState::root()).unwrap(); - - assert_eq!(get_value!(event.transaction!), "/foo/*/user/*/0/"); - assert_eq!( - get_value!(event.transaction_info.source!).as_str(), - "sanitized" - ); - - let remarks = get_value!(event!) - .transaction - .meta() - .iter_remarks() - .collect_vec(); - assert_debug_snapshot!(remarks, @r#"[ - Remark { - ty: Substituted, - rule_id: "int", - range: Some( - ( - 22, - 25, - ), - ), - }, - Remark { - ty: Substituted, - rule_id: "/foo/*/user/*/**", - range: None, - }, -]"#); - - assert_eq!( - get_value!(event.transaction_info.source!).as_str(), - "sanitized" - ); - - // Process again: - process_value(&mut event, &mut processor, ProcessingState::root()).unwrap(); - - assert_eq!(get_value!(event.transaction!), "/foo/*/user/*/0/"); - assert_eq!( - get_value!(event.transaction_info.source!).as_str(), - "sanitized" - ); - - let remarks = get_value!(event!) - .transaction - .meta() - .iter_remarks() - .collect_vec(); - assert_debug_snapshot!(remarks, @r#"[ - Remark { - ty: Substituted, - rule_id: "int", - range: Some( - ( - 22, - 25, - ), - ), - }, - Remark { - ty: Substituted, - rule_id: "/foo/*/user/*/**", - range: None, - }, -]"#); - - assert_eq!( - get_value!(event.transaction_info.source!).as_str(), - "sanitized" - ); - } - - #[test] - fn test_transaction_name_unsupported_source() { - let json = r#" - { - "type": "transaction", - "transaction": "/foo/2fd4e1c67a2d28fced849ee1bb76e7391b93eb12/user/123/0", - "transaction_info": { - "source": "foobar" - }, - "timestamp": "2021-04-26T08:00:00+0100", - "start_timestamp": "2021-04-26T07:59:01+0100", - "contexts": { - "trace": { - "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", - "span_id": "fa90fdead5f74053", - "op": "rails.request", - "status": "ok" - } - } - } - "#; - let mut event = Annotated::::from_json(json).unwrap(); - let rule1 = TransactionNameRule { - pattern: LazyGlob::new("/foo/*/**".to_string()), - expiry: Utc::now() + Duration::hours(1), - redaction: Default::default(), - }; - // This should not happend, such rules shouldn't be sent to relay at all. - let rule2 = TransactionNameRule { - pattern: LazyGlob::new("/*/**".to_string()), - expiry: Utc::now() + Duration::hours(1), - redaction: Default::default(), - }; - let rules = vec![rule1, rule2]; - - // This must not normalize transaction name, since it's disabled. - process_value( - &mut event, - &mut NormalizeProcessor::new(NormalizeProcessorConfig { - transaction_name_config: TransactionNameConfig { rules: &rules }, - ..Default::default() - }), - ProcessingState::root(), - ) - .unwrap(); - - assert_eq!( - get_value!(event.transaction!), - "/foo/2fd4e1c67a2d28fced849ee1bb76e7391b93eb12/user/123/0" - ); - assert!(get_value!(event!) - .transaction - .meta() - .iter_remarks() - .next() - .is_none()); - assert_eq!( - get_value!(event.transaction_info.source!).as_str(), - "foobar" - ); - } - - fn run_with_unknown_source(sdk: &str) -> Annotated { - let json = r#" - { - "type": "transaction", - "transaction": "/user/jane/blog/", - "timestamp": "2021-04-26T08:00:00+0100", - "start_timestamp": "2021-04-26T07:59:01+0100", - "contexts": { - "trace": { - "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", - "span_id": "fa90fdead5f74053", - "op": "rails.request", - "status": "ok" - } - } - } - "#; - let mut event = Annotated::::from_json(json).unwrap(); - event - .value_mut() - .as_mut() - .unwrap() - .client_sdk - .set_value(Some(ClientSdkInfo { - name: sdk.to_owned().into(), - ..Default::default() - })); - let rules: Vec = serde_json::from_value(serde_json::json!([ - {"pattern": "/user/*/**", "expiry": "3021-04-26T07:59:01+0100", "redaction": {"method": "replace"}} - ])) - .unwrap(); - - process_value( - &mut event, - &mut NormalizeProcessor::new(NormalizeProcessorConfig { - transaction_name_config: TransactionNameConfig { rules: &rules }, - ..Default::default() - }), - ProcessingState::root(), - ) - .unwrap(); - event - } - - #[test] - fn test_normalize_legacy_javascript() { - // Javascript without source annotation gets sanitized. - let event = run_with_unknown_source("sentry.javascript.browser"); - - assert_eq!(get_value!(event.transaction!), "/user/*/blog/"); - assert_eq!( - get_value!(event.transaction_info.source!).as_str(), - "sanitized" - ); - - let remarks = get_value!(event!) - .transaction - .meta() - .iter_remarks() - .collect_vec(); - assert_debug_snapshot!(remarks, @r#"[ - Remark { - ty: Substituted, - rule_id: "/user/*/**", - range: None, - }, -]"#); - - assert_eq!( - get_value!(event.transaction_info.source!).as_str(), - "sanitized" - ); - } - - #[test] - fn test_normalize_legacy_python() { - // Python without source annotation does not get sanitized, because we assume it to be - // low cardinality. - let event = run_with_unknown_source("sentry.python"); - assert_eq!(get_value!(event.transaction!), "/user/jane/blog/"); - assert_eq!( - get_value!(event.transaction_info.source!).as_str(), - "unknown" - ); - } - - #[test] - fn test_transaction_name_rename_end_slash() { - let json = r#" - { - "type": "transaction", - "transaction": "/foo/rule-target/user", - "transaction_info": { - "source": "url" - }, - "timestamp": "2021-04-26T08:00:00+0100", - "start_timestamp": "2021-04-26T07:59:01+0100", - "contexts": { - "trace": { - "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", - "span_id": "fa90fdead5f74053", - "op": "rails.request", - "status": "ok" - } - }, - "sdk": {"name": "sentry.ruby"}, - "modules": {"rack": "1.2.3"} - } - "#; - - let rule = TransactionNameRule { - pattern: LazyGlob::new("/foo/*/**".to_string()), - expiry: Utc::now() + Duration::hours(1), - redaction: Default::default(), - }; - - let mut event = Annotated::::from_json(json).unwrap(); - - process_value( - &mut event, - &mut NormalizeProcessor::new(NormalizeProcessorConfig { - transaction_name_config: TransactionNameConfig { rules: &[rule] }, - ..Default::default() - }), - ProcessingState::root(), - ) - .unwrap(); - - assert_eq!(get_value!(event.transaction!), "/foo/*/user"); - assert_eq!( - get_value!(event.transaction_info.source!).as_str(), - "sanitized" - ); - - let remarks = get_value!(event!) - .transaction - .meta() - .iter_remarks() - .collect_vec(); - assert_debug_snapshot!(remarks, @r#"[ - Remark { - ty: Substituted, - rule_id: "/foo/*/**", - range: None, - }, -]"#); - - assert_eq!( - get_value!(event.transaction_info.source!).as_str(), - "sanitized" - ); - } - - #[test] - fn test_normalize_transaction_names() { - let should_be_replaced = [ - "/aaa11111-aa11-11a1-a11a-1aaa1111a111", - "/1aa111aa-11a1-11aa-a111-a1a11111aa11", - "/00a00000-0000-0000-0000-000000000001", - "/test/b25feeaa-ed2d-4132-bcbd-6232b7922add/url", - ]; - let replaced = should_be_replaced.map(|s| { - let mut s = Annotated::new(s.to_owned()); - scrub_identifiers(&mut s).unwrap(); - s.0.unwrap() - }); - assert_eq!( - replaced, - ["/*", "/*", "/*", "/test/*/url",].map(str::to_owned) - ) - } - - macro_rules! transaction_name_test { - ($name:ident, $input:literal, $output:literal) => { - #[test] - fn $name() { - let json = format!( - r#" - {{ - "type": "transaction", - "transaction": "{}", - "transaction_info": {{ - "source": "url" - }}, - "timestamp": "2021-04-26T08:00:00+0100", - "start_timestamp": "2021-04-26T07:59:01+0100", - "contexts": {{ - "trace": {{ - "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", - "span_id": "fa90fdead5f74053", - "op": "rails.request", - "status": "ok" - }} - }} - }} - "#, - $input - ); - - let mut event = Annotated::::from_json(&json).unwrap(); - - process_value( - &mut event, - &mut NormalizeProcessor::default(), - ProcessingState::root(), - ) - .unwrap(); - - assert_eq!($output, event.value().unwrap().transaction.value().unwrap()); - } - }; - } - - transaction_name_test!(test_transaction_name_normalize_id, "/1234", "/*"); - transaction_name_test!( - test_transaction_name_normalize_in_segments_1, - "/user/path-with-1234/", - "/user/*/" - ); - transaction_name_test!( - test_transaction_name_normalize_in_segments_2, - "/testing/open-19-close/1", - "/testing/*/1" - ); - transaction_name_test!( - test_transaction_name_normalize_in_segments_3, - "/testing/open19close/1", - "/testing/*/1" - ); - transaction_name_test!( - test_transaction_name_normalize_in_segments_4, - "/testing/asdf012/asdf034/asdf056", - "/testing/*/*/*" - ); - transaction_name_test!( - test_transaction_name_normalize_in_segments_5, - "/foo/test%A33/1234", - "/foo/test%A33/*" - ); - transaction_name_test!( - test_transaction_name_normalize_url_encode_1, - "/%2Ftest%2Fopen%20and%20help%2F1%0A", - "/%2Ftest%2Fopen%20and%20help%2F1%0A" - ); - transaction_name_test!( - test_transaction_name_normalize_url_encode_2, - "/this/1234/%E2%9C%85/foo/bar/098123908213", - "/this/*/%E2%9C%85/foo/bar/*" - ); - transaction_name_test!( - test_transaction_name_normalize_url_encode_3, - "/foo/hello%20world-4711/", - "/foo/*/" - ); - transaction_name_test!( - test_transaction_name_normalize_url_encode_4, - "/foo/hello%20world-0xdeadbeef/", - "/foo/*/" - ); - transaction_name_test!( - test_transaction_name_normalize_url_encode_5, - "/foo/hello%20world-4711/", - "/foo/*/" - ); - transaction_name_test!( - test_transaction_name_normalize_url_encode_6, - "/foo/hello%2Fworld/", - "/foo/hello%2Fworld/" - ); - transaction_name_test!( - test_transaction_name_normalize_url_encode_7, - "/foo/hello%201/", - "/foo/hello%201/" - ); - transaction_name_test!( - test_transaction_name_normalize_sha, - "/hash/4c79f60c11214eb38604f4ae0781bfb2/diff", - "/hash/*/diff" - ); - transaction_name_test!( - test_transaction_name_normalize_uuid, - "/u/7b25feea-ed2d-4132-bcbd-6232b7922add/edit", - "/u/*/edit" - ); - transaction_name_test!( - test_transaction_name_normalize_hex, - "/u/0x3707344A4093822299F31D008/profile/123123213", - "/u/*/profile/*" - ); - transaction_name_test!( - test_transaction_name_normalize_windows_path, - r"C:\\\\Program Files\\1234\\Files", - r"C:\\Program Files\*\Files" - ); - transaction_name_test!(test_transaction_name_skip_replace_all, "12345", "12345"); - transaction_name_test!( - test_transaction_name_skip_replace_all2, - "open-12345-close", - "open-12345-close" - ); - - #[test] - fn test_scrub_identifiers_before_rules() { - // There's a rule matching the transaction name. However, the UUID - // should be scrubbed first. Scrubbing the UUID makes the rule to not - // match the transformed transaction name anymore. - - let mut event = Annotated::::from_json( - r#"{ - "type": "transaction", - "transaction": "/remains/rule-target/1234567890", - "transaction_info": { - "source": "url" - }, - "timestamp": "2021-04-26T08:00:00+0100", - "start_timestamp": "2021-04-26T07:59:01+0100", - "contexts": { - "trace": { - "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", - "span_id": "fa90fdead5f74053" - } - } - }"#, - ) - .unwrap(); - - process_value( - &mut event, - &mut NormalizeProcessor::new(NormalizeProcessorConfig { - transaction_name_config: TransactionNameConfig { - rules: &[TransactionNameRule { - pattern: LazyGlob::new("/remains/*/1234567890/".to_owned()), - expiry: Utc.with_ymd_and_hms(3000, 1, 1, 1, 1, 1).unwrap(), - redaction: RedactionRule::default(), - }], - }, - ..Default::default() - }), - ProcessingState::root(), - ) - .unwrap(); - - assert_eq!(get_value!(event.transaction!), "/remains/rule-target/*"); - assert_eq!( - get_value!(event.transaction_info.source!).as_str(), - "sanitized" - ); - - let remarks = get_value!(event!) - .transaction - .meta() - .iter_remarks() - .collect_vec(); - assert_debug_snapshot!(remarks, @r#"[ - Remark { - ty: Substituted, - rule_id: "int", - range: Some( - ( - 21, - 31, - ), - ), - }, -]"#); - assert_eq!( - get_value!(event.transaction_info.source!).as_str(), - "sanitized" - ); - } - - #[test] - fn test_scrub_identifiers_and_apply_rules() { - // Ensure rules are applied after scrubbing identifiers. Rules are only - // applied when `transaction.source="url"`, so this test ensures this - // value isn't set as part of identifier scrubbing. - let mut event = Annotated::::from_json( - r#"{ - "type": "transaction", - "transaction": "/remains/rule-target/1234567890", - "transaction_info": { - "source": "url" - }, - "timestamp": "2021-04-26T08:00:00+0100", - "start_timestamp": "2021-04-26T07:59:01+0100", - "contexts": { - "trace": { - "trace_id": "4c79f60c11214eb38604f4ae0781bfb2", - "span_id": "fa90fdead5f74053" - } - } - }"#, - ) - .unwrap(); - - process_value( - &mut event, - &mut NormalizeProcessor::new(NormalizeProcessorConfig { - transaction_name_config: TransactionNameConfig { - rules: &[TransactionNameRule { - pattern: LazyGlob::new("/remains/*/**".to_owned()), - expiry: Utc.with_ymd_and_hms(3000, 1, 1, 1, 1, 1).unwrap(), - redaction: RedactionRule::default(), - }], - }, - ..Default::default() - }), - ProcessingState::root(), - ) - .unwrap(); - - assert_eq!(get_value!(event.transaction!), "/remains/*/*"); - assert_eq!( - get_value!(event.transaction_info.source!).as_str(), - "sanitized" - ); - - let remarks = get_value!(event!) - .transaction - .meta() - .iter_remarks() - .collect_vec(); - assert_debug_snapshot!(remarks, @r#"[ - Remark { - ty: Substituted, - rule_id: "int", - range: Some( - ( - 21, - 31, - ), - ), - }, - Remark { - ty: Substituted, - rule_id: "/remains/*/**", - range: None, - }, -]"#); - } } From c31fea235563507c0d5e72ea420b250e50feb3de Mon Sep 17 00:00:00 2001 From: Iker Barriocanal <32816711+iker-barriocanal@users.noreply.github.com> Date: Thu, 16 Nov 2023 13:47:32 +0100 Subject: [PATCH 2/2] rename module processor -> utils --- relay-event-normalization/src/transactions/mod.rs | 4 ++-- .../src/transactions/{processor.rs => utils.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename relay-event-normalization/src/transactions/{processor.rs => utils.rs} (100%) diff --git a/relay-event-normalization/src/transactions/mod.rs b/relay-event-normalization/src/transactions/mod.rs index 5e3663f3871..ff5975b5d76 100644 --- a/relay-event-normalization/src/transactions/mod.rs +++ b/relay-event-normalization/src/transactions/mod.rs @@ -1,5 +1,5 @@ -mod processor; mod rules; +mod utils; -pub use processor::*; pub use rules::*; +pub use utils::*; diff --git a/relay-event-normalization/src/transactions/processor.rs b/relay-event-normalization/src/transactions/utils.rs similarity index 100% rename from relay-event-normalization/src/transactions/processor.rs rename to relay-event-normalization/src/transactions/utils.rs