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

feat(replays): add replay_id onto event from dynamic sampling context #1983

Merged
merged 23 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Metrics:
**Features**:

- Allow monitor checkins to paass `monitor_config` for monitor upserts. ([#1962](https://github.com/getsentry/relay/pull/1962))
- Add replay_id onto event from dynamic sampling context. ([#1983](https://github.com/getsentry/relay/pull/1983))
- Add product-name for devices, derived from the android model. ([#2004](https://github.com/getsentry/relay/pull/2004))
- Changes how device class is determined for iPhone devices. Instead of checking processor frequency, the device model is mapped to a device class. ([#1970](https://github.com/getsentry/relay/pull/1970))
- Don't sanitize transactions if no clustering rules exist and no UUIDs were scrubbed. ([#1976](https://github.com/getsentry/relay/pull/1976))
Expand Down
1 change: 1 addition & 0 deletions relay-general/benches/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ fn bench_store_processor(c: &mut Criterion) {
breakdowns: None,
span_attributes: Default::default(),
client_sample_rate: None,
replay_id: None,
client_hints: ClientHints::default(),
};

Expand Down
5 changes: 5 additions & 0 deletions relay-general/src/protocol/contexts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ mod os;
pub use os::*;
mod profile;
pub use profile::*;
mod replay;
pub use replay::*;
mod reprocessing;
pub use reprocessing::*;
mod response;
Expand Down Expand Up @@ -57,6 +59,8 @@ pub enum Context {
Trace(Box<TraceContext>),
/// Information related to Profiling.
Profile(Box<ProfileContext>),
/// Information related to Replay.
Replay(Box<ReplayContext>),
/// Information related to Monitors feature.
Monitor(Box<MonitorContext>),
/// Auxilliary information for reprocessing.
Expand Down Expand Up @@ -89,6 +93,7 @@ impl Context {
Context::Trace(_) => Some(TraceContext::default_key()),
Context::Profile(_) => Some(ProfileContext::default_key()),
Context::Monitor(_) => Some(MonitorContext::default_key()),
Context::Replay(_) => Some(ReplayContext::default_key()),
Context::Response(_) => Some(ResponseContext::default_key()),
Context::Otel(_) => Some(OtelContext::default_key()),
Context::CloudResource(_) => Some(CloudResourceContext::default_key()),
Expand Down
61 changes: 61 additions & 0 deletions relay-general/src/protocol/contexts/replay.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use crate::protocol::EventId;
use crate::types::{Annotated, Object, Value};

/// Replay context.
///
/// The replay context contains the replay_id of the session replay if the event
/// occurred during a replay. The replay_id is added onto the dynamic sampling context
/// on the javascript SDK which propagates it through the trace. In relay, we take
/// this value from the DSC and create a context which contains only the replay_id
/// This context is never set on the client for events, only on relay.
#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
#[cfg_attr(feature = "jsonschema", derive(JsonSchema))]
pub struct ReplayContext {
/// The replay ID.
pub replay_id: Annotated<EventId>,
/// Additional arbitrary fields for forwards compatibility.
#[metastructure(additional_properties, retain = "true")]
pub other: Object<Value>,
}
JoshFerge marked this conversation as resolved.
Show resolved Hide resolved

impl ReplayContext {
/// The key under which a replay context is generally stored (in `Contexts`).
pub fn default_key() -> &'static str {
"replay"
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::Context;

#[test]
pub(crate) fn test_trace_context_roundtrip() {
let json = r#"{
"replay_id": "4c79f60c11214eb38604f4ae0781bfb2",
"type": "replay"
}"#;
let context = Annotated::new(Context::Replay(Box::new(ReplayContext {
replay_id: Annotated::new(EventId("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap())),
other: Object::default(),
})));

assert_eq!(context, Annotated::from_json(json).unwrap());
assert_eq!(json, context.to_json_pretty().unwrap());
}

#[test]
pub(crate) fn test_replay_context_normalization() {
let json = r#"{
"replay_id": "4C79F60C11214EB38604F4AE0781BFB2",
"type": "replay"
}"#;
let context = Annotated::new(Context::Replay(Box::new(ReplayContext {
replay_id: Annotated::new(EventId("4c79f60c11214eb38604f4ae0781bfb2".parse().unwrap())),
other: Object::default(),
})));

assert_eq!(context, Annotated::from_json(json).unwrap());
}
}
3 changes: 3 additions & 0 deletions relay-general/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::collections::BTreeSet;
use std::sync::Arc;

use chrono::{DateTime, Utc};
use relay_common::Uuid;
use serde::{Deserialize, Serialize};
use serde_json::Value;

Expand Down Expand Up @@ -66,6 +67,8 @@ pub struct StoreConfig {

/// The SDK's sample rate as communicated via envelope headers.
pub client_sample_rate: Option<f64>,
/// The replay_id associated with the current event communicated via envelope headers.
pub replay_id: Option<Uuid>,
}

/// The processor that normalizes events for store.
Expand Down
48 changes: 47 additions & 1 deletion relay-general/src/store/normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ use crate::processor::{MaxChars, ProcessValue, ProcessingState, Processor};
use crate::protocol::{
self, AsPair, Breadcrumb, ClientSdkInfo, Context, Contexts, DebugImage, DeviceClass, Event,
EventId, EventType, Exception, Frame, Headers, IpAddr, Level, LogEntry, Measurement,
Measurements, Request, SpanStatus, Stacktrace, Tags, TraceContext, User, VALID_PLATFORMS,
Measurements, ReplayContext, Request, SpanStatus, Stacktrace, Tags, TraceContext, User,
VALID_PLATFORMS,
};
use crate::store::{ClockDriftProcessor, GeoIpLookup, StoreConfig, TransactionNameConfig};
use crate::types::{
Expand Down Expand Up @@ -199,6 +200,16 @@ impl<'a> NormalizeProcessor<'a> {
}
}
}
fn normalize_replay_context(&self, event: &mut Event) {
if let Some(ref mut contexts) = event.contexts.value_mut() {
if let Some(replay_id) = self.config.replay_id {
contexts.add(Context::Replay(Box::new(ReplayContext {
replay_id: Annotated::new(EventId(replay_id)),
other: Object::default(),
})));
}
}
}

/// Infers the `EventType` from the event's interfaces.
fn infer_event_type(&self, event: &Event) -> EventType {
Expand Down Expand Up @@ -821,6 +832,7 @@ impl<'a> Processor for NormalizeProcessor<'a> {
// Normalize connected attributes and interfaces
self.normalize_spans(event);
self.normalize_trace_context(event);
self.normalize_replay_context(event);

Ok(())
}
Expand Down Expand Up @@ -1123,6 +1135,7 @@ fn remove_logger_word(tokens: &mut Vec<&str>) {
mod tests {
use chrono::TimeZone;
use insta::assert_debug_snapshot;
use relay_common::Uuid;
use serde_json::json;
use similar_asserts::assert_eq;

Expand Down Expand Up @@ -1471,6 +1484,39 @@ mod tests {
process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();
assert_eq!(get_value!(event.environment), None);
}
#[test]
fn test_replay_id_added_from_dsc() {
let replay_id = Uuid::new_v4();
let mut event = Annotated::new(Event {
contexts: Annotated::new(Contexts(Object::new())),
..Event::default()
});
let config = StoreConfig {
replay_id: Some(replay_id),
..StoreConfig::default()
};
let mut processor = NormalizeProcessor::new(Arc::new(config), None);
let config = LightNormalizationConfig::default();
light_normalize_event(&mut event, config).unwrap();
process_value(&mut event, &mut processor, ProcessingState::root()).unwrap();

let event = event.value().unwrap();

assert_eq!(
event.contexts,
Annotated::new(Contexts({
let mut contexts = Object::new();
contexts.insert(
"replay".to_owned(),
Annotated::new(ContextInner(Context::Replay(Box::new(ReplayContext {
replay_id: Annotated::new(EventId(replay_id)),
other: Object::default(),
})))),
);
contexts
}))
)
}

#[test]
fn test_none_environment_errors() {
Expand Down
26 changes: 26 additions & 0 deletions relay-general/tests/snapshots/test_fixtures__event_schema.snap
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,9 @@ expression: "relay_general::protocol::event_json_schema()"
{
"$ref": "#/definitions/ProfileContext"
},
{
"$ref": "#/definitions/ReplayContext"
},
{
"$ref": "#/definitions/MonitorContext"
},
Expand Down Expand Up @@ -2736,6 +2739,29 @@ expression: "relay_general::protocol::event_json_schema()"
"RegVal": {
"type": "string"
},
"ReplayContext": {
"description": " Replay context.\n\n The replay context contains the replay_id of the session replay if the event\n occurred during a replay. The replay_id is added onto the dynamic sampling context\n on the javascript SDK which propagates it through the trace. In relay, we take\n this value from the DSC and create a context which contains only the replay_id\n This context is never set on the client for events, only on relay.",
"anyOf": [
{
"type": "object",
"properties": {
"replay_id": {
"description": " The replay ID.",
"default": null,
"anyOf": [
{
"$ref": "#/definitions/EventId"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
}
]
},
"Request": {
"description": " Http request information.\n\n The Request interface contains information on a HTTP request related to the event. In client\n SDKs, this can be an outgoing request, or the request that rendered the current web page. On\n server SDKs, this could be the incoming web request that is being handled.\n\n The data variable should only contain the request body (not the query string). It can either be\n a dictionary (for standard HTTP requests) or a raw request body.\n\n ### Ordered Maps\n\n In the Request interface, several attributes can either be declared as string, object, or list\n of tuples. Sentry attempts to parse structured information from the string representation in\n such cases.\n\n Sometimes, keys can be declared multiple times, or the order of elements matters. In such\n cases, use the tuple representation over a plain object.\n\n Example of request headers as object:\n\n ```json\n {\n \"content-type\": \"application/json\",\n \"accept\": \"application/json, application/xml\"\n }\n ```\n\n Example of the same headers as list of tuples:\n\n ```json\n [\n [\"content-type\", \"application/json\"],\n [\"accept\", \"application/json\"],\n [\"accept\", \"application/xml\"]\n ]\n ```\n\n Example of a fully populated request object:\n\n ```json\n {\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"http://absolute.uri/foo\",\n \"query_string\": \"query=foobar&page=2\",\n \"data\": {\n \"foo\": \"bar\"\n },\n \"cookies\": \"PHPSESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;\",\n \"headers\": {\n \"content-type\": \"text/html\"\n },\n \"env\": {\n \"REMOTE_ADDR\": \"192.168.0.1\"\n }\n }\n }\n ```",
"anyOf": [
Expand Down
Loading