1use std::fmt::Debug;
2use std::mem::size_of;
3use std::time::Duration;
4
5use chrono::{DateTime, Utc};
6use relay_quotas::{DataCategory, Scoping};
7use relay_system::Addr;
8
9use crate::envelope::{Envelope, Item};
10use crate::extractors::RequestMeta;
11use crate::managed::Counted as _;
12use crate::services::outcome::{DiscardReason, Outcome, TrackOutcome};
13use crate::statsd::{RelayCounters, RelayTimers};
14use crate::utils::EnvelopeSummary;
15
16#[derive(Clone, Copy, Debug)]
18enum Handling {
19 Success,
24 Failure,
26}
27
28impl Handling {
29 fn from_outcome(outcome: &Outcome) -> Self {
30 if outcome.is_unexpected() {
31 Self::Failure
32 } else {
33 Self::Success
34 }
35 }
36
37 fn as_str(&self) -> &str {
38 match self {
39 Handling::Success => "success",
40 Handling::Failure => "failure",
41 }
42 }
43}
44
45#[derive(Debug, Clone)]
47pub enum ItemAction {
48 Keep,
50 Drop(Outcome),
52 DropSilently,
54}
55
56#[derive(Debug)]
57struct EnvelopeContext {
58 summary: EnvelopeSummary,
59 scoping: Scoping,
60 partition_key: Option<u32>,
61 done: bool,
62}
63
64#[derive(Debug)]
80pub struct ManagedEnvelope {
81 envelope: Box<Envelope>,
82 context: EnvelopeContext,
83 outcome_aggregator: Addr<TrackOutcome>,
84}
85
86impl ManagedEnvelope {
87 pub fn new(envelope: Box<Envelope>, outcome_aggregator: Addr<TrackOutcome>) -> Self {
91 let meta = &envelope.meta();
92 let summary = EnvelopeSummary::compute(envelope.as_ref());
93 let scoping = meta.get_partial_scoping().into_scoping();
94
95 Self {
96 envelope,
97 context: EnvelopeContext {
98 summary,
99 scoping,
100 partition_key: None,
101 done: false,
102 },
103 outcome_aggregator,
104 }
105 }
106
107 #[cfg(test)]
109 pub fn untracked(envelope: Box<Envelope>, outcome_aggregator: Addr<TrackOutcome>) -> Self {
110 let mut envelope = Self::new(envelope, outcome_aggregator);
111 envelope.context.done = true;
112 envelope
113 }
114
115 pub fn envelope(&self) -> &Envelope {
117 self.envelope.as_ref()
118 }
119
120 pub fn envelope_mut(&mut self) -> &mut Envelope {
122 self.envelope.as_mut()
123 }
124
125 pub fn into_envelope(mut self) -> Box<Envelope> {
127 self.context.done = true;
128 self.take_envelope()
129 }
130
131 pub(crate) fn take_envelope(&mut self) -> Box<Envelope> {
135 Box::new(self.envelope.take_items())
136 }
137
138 pub fn update(&mut self) -> &mut Self {
142 self.context.summary = EnvelopeSummary::compute(self.envelope());
143 self
144 }
145
146 pub fn retain_items<F>(&mut self, mut f: F)
151 where
152 F: FnMut(&mut Item) -> ItemAction,
153 {
154 let mut outcomes = Vec::new();
155 self.envelope.retain_items(|item| match f(item) {
156 ItemAction::Keep => true,
157 ItemAction::DropSilently => false,
158 ItemAction::Drop(outcome) => {
159 for (category, quantity) in item.quantities() {
160 outcomes.push((outcome.clone(), category, quantity));
161 }
162
163 false
164 }
165 });
166 for (outcome, category, quantity) in outcomes {
167 self.track_outcome(outcome, category, quantity);
168 }
169 }
171
172 pub fn scope(&mut self, scoping: Scoping) -> &mut Self {
174 self.context.scoping = scoping;
175 self
176 }
177
178 pub fn reject_event(&mut self, outcome: Outcome) {
182 if let Some(event_category) = self.event_category() {
183 self.envelope.retain_items(|item| !item.creates_event());
184 if let Some(indexed) = event_category.index_category() {
185 self.track_outcome(outcome.clone(), indexed, 1);
186 }
187 self.track_outcome(outcome, event_category, 1);
188 }
189 }
190
191 pub fn track_outcome(&self, outcome: Outcome, category: DataCategory, quantity: usize) {
196 self.outcome_aggregator.send(TrackOutcome {
197 timestamp: self.received_at(),
198 scoping: self.context.scoping,
199 outcome,
200 event_id: self.envelope.event_id(),
201 remote_addr: self.meta().remote_addr(),
202 category,
203 quantity: quantity as u64,
204 });
205 }
206
207 pub fn accept(mut self) {
213 if !self.context.done {
214 self.finish(RelayCounters::EnvelopeAccepted, Handling::Success);
215 }
216 }
217
218 fn event_category(&self) -> Option<DataCategory> {
220 self.context.summary.event_category
221 }
222
223 pub fn reject(&mut self, outcome: Outcome) {
227 if self.context.done {
228 return;
229 }
230
231 let handling = Handling::from_outcome(&outcome);
234 match handling {
235 Handling::Success => relay_log::debug!("dropped envelope: {outcome}"),
236 Handling::Failure => {
237 let summary = &self.context.summary;
238
239 relay_log::error!(
240 tags.project_key = self.scoping().project_key.to_string(),
241 tags.has_attachments = summary.attachment_quantities.bytes() > 0,
242 tags.has_sessions = summary.session_quantity > 0,
243 tags.has_profiles = summary.profile_quantity.total > 0,
244 tags.has_transactions = summary.secondary_transaction_quantity > 0,
245 tags.has_span_metrics = summary.secondary_span_quantity > 0,
246 tags.has_replays = summary.replay_quantity > 0,
247 tags.has_user_reports = summary.user_report_quantity > 0,
248 tags.has_trace_metrics = summary.trace_metric_quantity > 0,
249 tags.has_checkins = summary.monitor_quantity > 0,
250 tags.event_category = ?summary.event_category,
251 cached_summary = ?summary,
252 recomputed_summary = ?EnvelopeSummary::compute(self.envelope()),
253 "dropped envelope: {outcome}"
254 );
255 }
256 }
257
258 for (category, quantity) in self.context.summary.quantities() {
259 self.track_outcome(outcome.clone(), category, quantity);
260 }
261
262 self.finish(RelayCounters::EnvelopeRejected, handling);
263 }
264
265 pub fn scoping(&self) -> Scoping {
267 self.context.scoping
268 }
269
270 pub fn partition_key(&self) -> Option<u32> {
272 self.context.partition_key
273 }
274
275 pub fn set_partition_key(&mut self, partition_key: Option<u32>) -> &mut Self {
277 self.context.partition_key = partition_key;
278 self
279 }
280
281 pub fn meta(&self) -> &RequestMeta {
283 self.envelope().meta()
284 }
285
286 pub fn estimated_size(&self) -> usize {
295 (f64::ceil(
297 (self.context.summary.payload_size + size_of::<Self>() + size_of::<Envelope>()) as f64
298 / 1000.,
299 ) * 1000.) as usize
300 }
301
302 pub fn received_at(&self) -> DateTime<Utc> {
306 self.envelope.received_at()
307 }
308
309 pub fn age(&self) -> Duration {
313 self.envelope.age()
314 }
315
316 pub(super) fn outcome_aggregator(&self) -> &Addr<TrackOutcome> {
319 &self.outcome_aggregator
320 }
321
322 fn finish(&mut self, counter: RelayCounters, handling: Handling) {
324 relay_statsd::metric!(counter(counter) += 1, handling = handling.as_str());
325 relay_statsd::metric!(timer(RelayTimers::EnvelopeTotalTime) = self.age());
326
327 self.context.done = true;
328 }
329}
330
331impl Drop for ManagedEnvelope {
332 fn drop(&mut self) {
333 self.reject(Outcome::Invalid(DiscardReason::Internal));
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340 use bytes::Bytes;
341
342 #[test]
343 fn span_metrics_are_reported() {
344 let bytes =
345 Bytes::from(r#"{"dsn":"https://e12d836b15bb49d7bbf99e64295d995b:@sentry.io/42"}"#);
346 let envelope = Envelope::parse_bytes(bytes).unwrap();
347
348 let (outcome_aggregator, mut rx) = Addr::custom();
349 let mut env = ManagedEnvelope::new(envelope, outcome_aggregator);
350 env.context.summary.span_quantity = 123;
351 env.context.summary.secondary_span_quantity = 456;
352
353 env.reject(Outcome::Abuse);
354
355 rx.close();
356
357 let outcome = rx.blocking_recv().unwrap();
358 assert_eq!(outcome.category, DataCategory::Span);
359 assert_eq!(outcome.quantity, 123);
360 assert_eq!(outcome.outcome, Outcome::Abuse);
361
362 let outcome = rx.blocking_recv().unwrap();
363 assert_eq!(outcome.category, DataCategory::SpanIndexed);
364 assert_eq!(outcome.quantity, 123);
365 assert_eq!(outcome.outcome, Outcome::Abuse);
366
367 let outcome = rx.blocking_recv().unwrap();
368 assert_eq!(outcome.category, DataCategory::Span);
369 assert_eq!(outcome.quantity, 456);
370 assert_eq!(outcome.outcome, Outcome::Abuse);
371
372 assert!(rx.blocking_recv().is_none());
373 }
374}