Skip to content

Commit 1b36c57

Browse files
committed
feat(replays): add replay events itemtype
1 parent b910e80 commit 1b36c57

File tree

16 files changed

+202
-7
lines changed

16 files changed

+202
-7
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
- Add sampling + tagging by event platform and transaction op. Some (unused) tagging rules from 22.4.0 have been renamed. ([#1231](https://github.com/getsentry/relay/pull/1231))
1313
- Add ReplayRecording ItemType. ([#1236](https://github.com/getsentry/relay/pull/1236))
14+
- Add ReplayEvent ItemType. ([#1239](https://github.com/getsentry/relay/pull/1239))
1415

1516
## 22.4.0
1617

relay-common/src/constants.rs

+8
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ pub enum EventType {
3636
ExpectStaple,
3737
/// Performance monitoring transactions carrying spans.
3838
Transaction,
39+
/// Replay Events for session replays
40+
ReplayEvent,
3941
/// All events that do not qualify as any other type.
4042
#[serde(other)]
4143
Default,
@@ -86,6 +88,7 @@ impl fmt::Display for EventType {
8688
EventType::ExpectCt => write!(f, "expectct"),
8789
EventType::ExpectStaple => write!(f, "expectstaple"),
8890
EventType::Transaction => write!(f, "transaction"),
91+
EventType::ReplayEvent => write!(f, "replay_event"),
8992
}
9093
}
9194
}
@@ -109,6 +112,8 @@ pub enum DataCategory {
109112
Session = 5,
110113
/// Reserved data category that shall not appear in the outcomes.
111114
Internal = -2,
115+
/// Replay Events
116+
ReplayEvent = 6,
112117
/// Any other data category not known by this Relay.
113118
#[serde(other)]
114119
Unknown = -1,
@@ -125,6 +130,7 @@ impl DataCategory {
125130
"attachment" => Self::Attachment,
126131
"session" => Self::Session,
127132
"internal" => Self::Internal,
133+
"replay_event" => Self::ReplayEvent,
128134
_ => Self::Unknown,
129135
}
130136
}
@@ -139,6 +145,7 @@ impl DataCategory {
139145
Self::Attachment => "attachment",
140146
Self::Session => "session",
141147
Self::Internal => "internal",
148+
Self::ReplayEvent => "replay_event",
142149
Self::Unknown => "unknown",
143150
}
144151
}
@@ -175,6 +182,7 @@ impl From<EventType> for DataCategory {
175182
match ty {
176183
EventType::Default | EventType::Error => Self::Error,
177184
EventType::Transaction => Self::Transaction,
185+
EventType::ReplayEvent => Self::ReplayEvent,
178186
EventType::Csp | EventType::Hpkp | EventType::ExpectCt | EventType::ExpectStaple => {
179187
Self::Security
180188
}

relay-config/src/config.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -767,8 +767,10 @@ pub enum KafkaTopic {
767767
Metrics,
768768
/// Profiles
769769
Profiles,
770-
/// ReplayRecordings, large blobs sent by the replay sdk
770+
/// ReplayRecordings, large blobs
771771
ReplayRecordings,
772+
/// ReplayEvents, breadcrumb + session updates for replays
773+
ReplayEvents,
772774
}
773775

774776
/// Configuration for topics.
@@ -793,6 +795,8 @@ pub struct TopicAssignments {
793795
pub profiles: TopicAssignment,
794796
/// Recordings topic name.
795797
pub replay_recordings: TopicAssignment,
798+
/// Replay Events topic name.
799+
pub replay_events: TopicAssignment,
796800
}
797801

798802
impl TopicAssignments {
@@ -808,6 +812,7 @@ impl TopicAssignments {
808812
KafkaTopic::Metrics => &self.metrics,
809813
KafkaTopic::Profiles => &self.profiles,
810814
KafkaTopic::ReplayRecordings => &self.replay_recordings,
815+
KafkaTopic::ReplayEvents => &self.replay_events,
811816
}
812817
}
813818
}
@@ -824,6 +829,7 @@ impl Default for TopicAssignments {
824829
metrics: "ingest-metrics".to_owned().into(),
825830
profiles: "profiles".to_owned().into(),
826831
replay_recordings: "ingest-replay-recordings".to_owned().into(),
832+
replay_events: "ingest-replay-events".to_owned().into(),
827833
}
828834
}
829835
}

relay-general/src/store/normalize.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ impl<'a> NormalizeProcessor<'a> {
114114

115115
/// Ensure measurements interface is only present for transaction events
116116
fn normalize_measurements(&self, event: &mut Event) {
117-
if event.ty.value() != Some(&EventType::Transaction) {
117+
if event.ty.value() != Some(&EventType::Transaction)
118+
&& event.ty.value() != Some(&EventType::ReplayEvent)
119+
{
118120
// Only transaction events may have a measurements interface
119121
event.measurements = Annotated::empty();
120122
}
@@ -129,7 +131,9 @@ impl<'a> NormalizeProcessor<'a> {
129131
}
130132

131133
fn normalize_spans(&self, event: &mut Event) {
132-
if event.ty.value() == Some(&EventType::Transaction) {
134+
if event.ty.value() == Some(&EventType::Transaction)
135+
|| event.ty.value() == Some(&EventType::ReplayEvent)
136+
{
133137
spans::normalize_spans(event, &self.config.span_attributes);
134138
}
135139
}
@@ -260,6 +264,9 @@ impl<'a> NormalizeProcessor<'a> {
260264
if event.ty.value() == Some(&EventType::Transaction) {
261265
return EventType::Transaction;
262266
}
267+
if event.ty.value() == Some(&EventType::ReplayEvent) {
268+
return EventType::ReplayEvent;
269+
}
263270

264271
// The SDKs do not describe event types, and we must infer them from available attributes.
265272
let has_exceptions = event

relay-general/tests/snapshots/test_fixtures__event_schema.snap

+1
Original file line numberDiff line numberDiff line change
@@ -1228,6 +1228,7 @@ expression: event_json_schema()
12281228
"expectct",
12291229
"expectstaple",
12301230
"transaction",
1231+
"replayevent",
12311232
"default"
12321233
]
12331234
},

relay-quotas/src/quota.rs

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ impl CategoryUnit {
103103
DataCategory::Default
104104
| DataCategory::Error
105105
| DataCategory::Transaction
106+
| DataCategory::ReplayEvent
106107
| DataCategory::Security => Some(Self::Count),
107108
DataCategory::Attachment => Some(Self::Bytes),
108109
DataCategory::Session => Some(Self::Batched),

relay-server/src/actors/envelopes.rs

+8
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,7 @@ impl EnvelopeProcessor {
12831283
ItemType::ClientReport => false,
12841284
ItemType::Profile => false,
12851285
ItemType::ReplayRecording => false,
1286+
ItemType::ReplayEvent => false,
12861287
}
12871288
}
12881289

@@ -1302,6 +1303,7 @@ impl EnvelopeProcessor {
13021303
// `process_event`.
13031304
let event_item = envelope.take_item_by(|item| item.ty() == ItemType::Event);
13041305
let transaction_item = envelope.take_item_by(|item| item.ty() == ItemType::Transaction);
1306+
let replay_item = envelope.take_item_by(|item| item.ty() == ItemType::ReplayEvent);
13051307
let security_item = envelope.take_item_by(|item| item.ty() == ItemType::Security);
13061308
let raw_security_item = envelope.take_item_by(|item| item.ty() == ItemType::RawSecurity);
13071309
let form_item = envelope.take_item_by(|item| item.ty() == ItemType::FormData);
@@ -1333,6 +1335,12 @@ impl EnvelopeProcessor {
13331335
// hint to normalization that we're dealing with a transaction now.
13341336
self.event_from_json_payload(item, Some(EventType::Transaction))?
13351337
})
1338+
} else if let Some(mut item) = replay_item {
1339+
relay_log::trace!("processing json replay event");
1340+
state.sample_rates = item.take_sample_rates();
1341+
metric!(timer(RelayTimers::EventProcessingDeserialize), {
1342+
self.event_from_json_payload(item, Some(EventType::ReplayEvent))?
1343+
})
13361344
} else if let Some(mut item) = raw_security_item {
13371345
relay_log::trace!("processing security report");
13381346
state.sample_rates = item.take_sample_rates();

relay-server/src/actors/store.rs

+49
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ struct Producers {
6060
metrics: Producer,
6161
profiles: Producer,
6262
replay_recordings: Producer,
63+
replay_events: Producer,
6364
}
6465

6566
impl Producers {
@@ -78,6 +79,7 @@ impl Producers {
7879
KafkaTopic::Metrics => Some(&self.metrics),
7980
KafkaTopic::Profiles => Some(&self.profiles),
8081
KafkaTopic::ReplayRecordings => Some(&self.replay_recordings),
82+
KafkaTopic::ReplayEvents => Some(&self.replay_events),
8183
}
8284
}
8385
}
@@ -140,6 +142,11 @@ impl StoreForwarder {
140142
&mut reused_producers,
141143
KafkaTopic::ReplayRecordings,
142144
)?,
145+
replay_events: make_producer(
146+
&*config,
147+
&mut reused_producers,
148+
KafkaTopic::ReplayEvents,
149+
)?,
143150
};
144151

145152
Ok(Self { config, producers })
@@ -193,6 +200,7 @@ impl StoreForwarder {
193200
id: id.clone(),
194201
chunk_index,
195202
});
203+
196204
self.produce(topic, attachment_message)?;
197205
offset += chunk_size;
198206
chunk_index += 1;
@@ -444,6 +452,27 @@ impl StoreForwarder {
444452
);
445453
Ok(())
446454
}
455+
fn produce_replay_event(
456+
&self,
457+
event_id: EventId,
458+
project_id: ProjectId,
459+
start_time: Instant,
460+
item: &Item,
461+
) -> Result<(), StoreError> {
462+
let message = ReplayEventKafkaMessage {
463+
event_id,
464+
project_id,
465+
start_time: UnixTimestamp::from_instant(start_time).as_secs(),
466+
payload: item.payload(),
467+
};
468+
relay_log::trace!("Sending replay event to Kafka");
469+
self.produce(KafkaTopic::ReplayEvents, KafkaMessage::ReplayEvent(message))?;
470+
metric!(
471+
counter(RelayCounters::ProcessingMessageProduced) += 1,
472+
event_type = "replay_event"
473+
);
474+
Ok(())
475+
}
447476
}
448477

449478
/// StoreMessageForwarder is an async actor since the only thing it does is put the messages
@@ -531,6 +560,17 @@ struct EventKafkaMessage {
531560
/// Attachments that are potentially relevant for processing.
532561
attachments: Vec<ChunkedAttachment>,
533562
}
563+
#[derive(Clone, Debug, Serialize)]
564+
struct ReplayEventKafkaMessage {
565+
/// Raw event payload.
566+
payload: Bytes,
567+
/// Time at which the event was received by Relay.
568+
start_time: u64,
569+
/// The event id.
570+
event_id: EventId,
571+
/// The project id for the current event.
572+
project_id: ProjectId,
573+
}
534574

535575
/// Container payload for chunks of attachments.
536576
#[derive(Debug, Serialize)]
@@ -631,6 +671,7 @@ enum KafkaMessage {
631671
Metric(MetricKafkaMessage),
632672
Profile(ProfileKafkaMessage),
633673
ReplayRecording(AttachmentKafkaMessage),
674+
ReplayEvent(ReplayEventKafkaMessage),
634675
}
635676

636677
impl KafkaMessage {
@@ -644,6 +685,7 @@ impl KafkaMessage {
644685
KafkaMessage::Metric(_) => "metric",
645686
KafkaMessage::Profile(_) => "profile",
646687
KafkaMessage::ReplayRecording(_) => "replay_recording",
688+
KafkaMessage::ReplayEvent(_) => "replay_event",
647689
}
648690
}
649691

@@ -658,6 +700,7 @@ impl KafkaMessage {
658700
Self::Metric(_message) => Uuid::nil(), // TODO(ja): Determine a partitioning key
659701
Self::Profile(_message) => Uuid::nil(),
660702
Self::ReplayRecording(_message) => Uuid::nil(),
703+
Self::ReplayEvent(_message) => Uuid::nil(),
661704
};
662705

663706
if uuid.is_nil() {
@@ -790,6 +833,12 @@ impl Handler<StoreEnvelope> for StoreForwarder {
790833
)?;
791834
replay_recordings.push(replay_recording);
792835
}
836+
ItemType::ReplayEvent => self.produce_replay_event(
837+
event_id.ok_or(StoreError::NoEventId)?,
838+
scoping.project_id,
839+
start_time,
840+
item,
841+
)?,
793842
_ => {}
794843
}
795844
}

relay-server/src/envelope.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,10 @@ pub enum ItemType {
104104
ClientReport,
105105
/// Profile event payload encoded in JSON
106106
Profile,
107-
/// Replay Recording blob payload
107+
/// Replay Recording payload
108108
ReplayRecording,
109+
/// Replay metadata and breadcrumb payload
110+
ReplayEvent,
109111
}
110112

111113
impl ItemType {
@@ -114,6 +116,7 @@ impl ItemType {
114116
match event_type {
115117
EventType::Default | EventType::Error => ItemType::Event,
116118
EventType::Transaction => ItemType::Transaction,
119+
EventType::ReplayEvent => ItemType::ReplayEvent,
117120
EventType::Csp | EventType::Hpkp | EventType::ExpectCt | EventType::ExpectStaple => {
118121
ItemType::Security
119122
}
@@ -139,6 +142,7 @@ impl fmt::Display for ItemType {
139142
Self::ClientReport => write!(f, "client report"),
140143
Self::Profile => write!(f, "profile"),
141144
Self::ReplayRecording => write!(f, "replay_recording"),
145+
Self::ReplayEvent => write!(f, "replay_event"),
142146
}
143147
}
144148
}
@@ -557,6 +561,7 @@ impl Item {
557561
| ItemType::Transaction
558562
| ItemType::Security
559563
| ItemType::RawSecurity
564+
| ItemType::ReplayEvent
560565
| ItemType::UnrealReport => true,
561566

562567
// Attachments are only event items if they are crash reports or if they carry partial
@@ -601,6 +606,7 @@ impl Item {
601606
ItemType::RawSecurity => true,
602607
ItemType::UnrealReport => true,
603608
ItemType::UserReport => true,
609+
ItemType::ReplayEvent => true,
604610
ItemType::Session => false,
605611
ItemType::Sessions => false,
606612
ItemType::Metrics => false,

relay-server/src/metrics_extraction/transactions.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,9 @@ fn extract_transaction_metrics_inner(
296296
event: &Event,
297297
mut push_metric: impl FnMut(Metric),
298298
) {
299-
if event.ty.value() != Some(&EventType::Transaction) {
299+
if event.ty.value() != Some(&EventType::Transaction)
300+
&& event.ty.value() != Some(&EventType::ReplayEvent)
301+
{
300302
return;
301303
}
302304

relay-server/src/utils/rate_limits.rs

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ fn infer_event_category(item: &Item) -> Option<DataCategory> {
105105
ItemType::UserReport => None,
106106
ItemType::Profile => None,
107107
ItemType::ReplayRecording => None,
108+
ItemType::ReplayEvent => None,
108109
// the following items are "internal" item types. From the perspective of the SDK
109110
// the use the "internal" data category however this data category is in fact never
110111
// supposed to be emitted by relay as internal items must not be rate limited. As

relay-server/src/utils/sizes.rs

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub fn check_envelope_size_limits(config: &Config, envelope: &Envelope) -> bool
2525
ItemType::Event
2626
| ItemType::Transaction
2727
| ItemType::Security
28+
| ItemType::ReplayEvent
2829
| ItemType::RawSecurity
2930
| ItemType::FormData => event_size += item.len(),
3031
ItemType::Attachment | ItemType::UnrealReport | ItemType::ReplayRecording => {

tests/integration/conftest.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@
2222
outcomes_consumer,
2323
transactions_consumer,
2424
attachments_consumer,
25-
replay_recordings_consumer,
2625
sessions_consumer,
2726
metrics_consumer,
27+
replay_recordings_consumer,
28+
replay_events_consumer,
2829
) # noqa
2930

3031

tests/integration/fixtures/__init__.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,6 @@ def send_envelope(self, project_id, envelope, headers=None, dsn_key_idx=0):
166166
"X-Sentry-Auth": self.get_auth_header(project_id, dsn_key_idx),
167167
**(headers or {}),
168168
}
169-
170169
response = self.post(url, headers=headers, data=envelope.serialize())
171170
response.raise_for_status()
172171

@@ -196,6 +195,14 @@ def send_transaction(self, project_id, payload, item_headers=None):
196195

197196
self.send_envelope(project_id, envelope)
198197

198+
def send_replay_event(self, project_id, payload, item_headers=None):
199+
envelope = Envelope()
200+
envelope.add_item(Item(payload=PayloadRef(json=payload), type="replay_event"))
201+
if envelope.headers is None:
202+
envelope.headers = {}
203+
204+
self.send_envelope(project_id, envelope)
205+
199206
def send_session_aggregates(self, project_id, payload):
200207
envelope = Envelope()
201208
envelope.add_item(Item(payload=PayloadRef(json=payload), type="sessions"))

0 commit comments

Comments
 (0)