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 drop_items_silently(&mut self) {
174 self.envelope.drop_items_silently();
175 }
176
177 pub fn scope(&mut self, scoping: Scoping) -> &mut Self {
179 self.context.scoping = scoping;
180 self
181 }
182
183 pub fn reject_event(&mut self, outcome: Outcome) {
187 if let Some(event_category) = self.event_category() {
188 self.envelope.retain_items(|item| !item.creates_event());
189 if let Some(indexed) = event_category.index_category() {
190 self.track_outcome(outcome.clone(), indexed, 1);
191 }
192 self.track_outcome(outcome, event_category, 1);
193 }
194 }
195
196 pub fn track_outcome(&self, outcome: Outcome, category: DataCategory, quantity: usize) {
201 self.outcome_aggregator.send(TrackOutcome {
202 timestamp: self.received_at(),
203 scoping: self.context.scoping,
204 outcome,
205 event_id: self.envelope.event_id(),
206 remote_addr: self.meta().remote_addr(),
207 category,
208 quantity: quantity as u64,
209 });
210 }
211
212 pub fn accept(mut self) {
218 if !self.context.done {
219 self.finish(RelayCounters::EnvelopeAccepted, Handling::Success);
220 }
221 }
222
223 fn event_category(&self) -> Option<DataCategory> {
225 self.context.summary.event_category
226 }
227
228 pub fn reject(&mut self, outcome: Outcome) {
232 if self.context.done {
233 return;
234 }
235
236 let handling = Handling::from_outcome(&outcome);
239 match handling {
240 Handling::Success => relay_log::debug!("dropped envelope: {outcome}"),
241 Handling::Failure => {
242 let summary = &self.context.summary;
243
244 relay_log::error!(
245 tags.project_key = self.scoping().project_key.to_string(),
246 tags.has_attachments = summary.attachment_quantities.bytes() > 0,
247 tags.has_sessions = summary.session_quantity > 0,
248 tags.has_profiles = summary.profile_quantity.total > 0,
249 tags.has_transactions = summary.secondary_transaction_quantity > 0,
250 tags.has_span_metrics = summary.secondary_span_quantity > 0,
251 tags.has_replays = summary.replay_quantity > 0,
252 tags.has_user_reports = summary.user_report_quantity > 0,
253 tags.has_trace_metrics = summary.trace_metric_quantity > 0,
254 tags.has_checkins = summary.monitor_quantity > 0,
255 tags.event_category = ?summary.event_category,
256 cached_summary = ?summary,
257 recomputed_summary = ?EnvelopeSummary::compute(self.envelope()),
258 "dropped envelope: {outcome}"
259 );
260 }
261 }
262
263 for (category, quantity) in self.context.summary.quantities() {
264 self.track_outcome(outcome.clone(), category, quantity);
265 }
266
267 self.finish(RelayCounters::EnvelopeRejected, handling);
268 }
269
270 pub fn scoping(&self) -> Scoping {
272 self.context.scoping
273 }
274
275 pub fn partition_key(&self) -> Option<u32> {
277 self.context.partition_key
278 }
279
280 pub fn set_partition_key(&mut self, partition_key: Option<u32>) -> &mut Self {
282 self.context.partition_key = partition_key;
283 self
284 }
285
286 pub fn meta(&self) -> &RequestMeta {
288 self.envelope().meta()
289 }
290
291 pub fn estimated_size(&self) -> usize {
300 (f64::ceil(
302 (self.context.summary.payload_size + size_of::<Self>() + size_of::<Envelope>()) as f64
303 / 1000.,
304 ) * 1000.) as usize
305 }
306
307 pub fn received_at(&self) -> DateTime<Utc> {
311 self.envelope.received_at()
312 }
313
314 pub fn age(&self) -> Duration {
318 self.envelope.age()
319 }
320
321 pub(super) fn outcome_aggregator(&self) -> &Addr<TrackOutcome> {
324 &self.outcome_aggregator
325 }
326
327 fn finish(&mut self, counter: RelayCounters, handling: Handling) {
329 relay_statsd::metric!(counter(counter) += 1, handling = handling.as_str());
330 relay_statsd::metric!(timer(RelayTimers::EnvelopeTotalTime) = self.age());
331
332 self.context.done = true;
333 }
334}
335
336impl Drop for ManagedEnvelope {
337 fn drop(&mut self) {
338 self.reject(Outcome::Invalid(DiscardReason::Internal));
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345 use bytes::Bytes;
346
347 #[test]
348 fn span_metrics_are_reported() {
349 let bytes =
350 Bytes::from(r#"{"dsn":"https://e12d836b15bb49d7bbf99e64295d995b:@sentry.io/42"}"#);
351 let envelope = Envelope::parse_bytes(bytes).unwrap();
352
353 let (outcome_aggregator, mut rx) = Addr::custom();
354 let mut env = ManagedEnvelope::new(envelope, outcome_aggregator);
355 env.context.summary.span_quantity = 123;
356 env.context.summary.secondary_span_quantity = 456;
357
358 env.reject(Outcome::Abuse);
359
360 rx.close();
361
362 let outcome = rx.blocking_recv().unwrap();
363 assert_eq!(outcome.category, DataCategory::Span);
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::SpanIndexed);
369 assert_eq!(outcome.quantity, 123);
370 assert_eq!(outcome.outcome, Outcome::Abuse);
371
372 let outcome = rx.blocking_recv().unwrap();
373 assert_eq!(outcome.category, DataCategory::Span);
374 assert_eq!(outcome.quantity, 456);
375 assert_eq!(outcome.outcome, Outcome::Abuse);
376
377 assert!(rx.blocking_recv().is_none());
378 }
379}