relay_server/utils/
sizes.rs

1use relay_config::Config;
2
3use crate::envelope::{AttachmentType, Envelope, ItemType};
4use crate::managed::{ItemAction, ManagedEnvelope};
5use crate::services::outcome::{DiscardAttachmentType, DiscardItemType};
6
7/// Checks for size limits of items in this envelope.
8///
9/// Returns `Ok`, if the envelope adheres to the configured size limits. Otherwise, returns
10/// an `Err` containing the offending item type, in which case the envelope should be discarded
11/// and a `413 Payload Too Large` response should be given.
12///
13/// The following limits are checked:
14///
15///  - `max_attachment_size`
16///  - `max_attachments_size`
17///  - `max_check_in_size`
18///  - `max_event_size`
19///  - `max_log_size`
20///  - `max_metric_buckets_size`
21///  - `max_profile_size`
22///  - `max_replay_compressed_size`
23///  - `max_session_count`
24///  - `max_span_size`
25///  - `max_statsd_size`
26///  - `max_container_size`
27///  - `max_span_count`
28///  - `max_log_count`
29pub fn check_envelope_size_limits(
30    config: &Config,
31    envelope: &Envelope,
32) -> Result<(), DiscardItemType> {
33    const NO_LIMIT: usize = usize::MAX;
34
35    let mut event_size = 0;
36    let mut attachments_size = 0;
37    let mut session_count = 0;
38    let mut span_count = 0;
39    let mut log_count = 0;
40    let mut client_reports_size = 0;
41
42    for item in envelope.items() {
43        if item.is_container() && item.len() > config.max_container_size() {
44            return Err(item.ty().into());
45        }
46
47        let max_size = match item.ty() {
48            ItemType::Event
49            | ItemType::Transaction
50            | ItemType::Security
51            | ItemType::ReplayEvent
52            | ItemType::RawSecurity
53            | ItemType::Nel
54            | ItemType::UserReportV2
55            | ItemType::FormData => {
56                event_size += item.len();
57                NO_LIMIT
58            }
59            ItemType::Attachment | ItemType::UnrealReport | ItemType::UserReport => {
60                attachments_size += item.len();
61                config.max_attachment_size()
62            }
63            ItemType::ReplayRecording => config.max_replay_compressed_size(),
64            ItemType::ReplayVideo => config.max_replay_compressed_size(),
65            ItemType::Session | ItemType::Sessions => {
66                session_count += 1;
67                NO_LIMIT
68            }
69            ItemType::ClientReport => {
70                client_reports_size += item.len();
71                NO_LIMIT
72            }
73            ItemType::Profile => config.max_profile_size(),
74            ItemType::CheckIn => config.max_check_in_size(),
75            ItemType::Statsd => config.max_statsd_size(),
76            ItemType::MetricBuckets => config.max_metric_buckets_size(),
77            ItemType::Log => {
78                log_count += item.item_count().unwrap_or(1) as usize;
79                config.max_log_size()
80            }
81            ItemType::Span | ItemType::OtelSpan => {
82                span_count += item.item_count().unwrap_or(1) as usize;
83                config.max_span_size()
84            }
85            ItemType::OtelTracesData => config.max_event_size(), // a spans container similar to `Transaction`
86            ItemType::OtelLogsData => config.max_event_size(), // a logs container similar to `Transaction`
87            ItemType::ProfileChunk => config.max_profile_size(),
88            ItemType::Unknown(_) => NO_LIMIT,
89        };
90
91        // For item containers, we want to check that the contained items obey
92        // the size limits *on average*.
93        // For standalone items, this is just the item size itself.
94        let avg_item_size = item.len() / (item.item_count().unwrap_or(1).max(1) as usize);
95        if avg_item_size > max_size {
96            return Err(item
97                .attachment_type()
98                .map(|t| t.into())
99                .unwrap_or_else(|| item.ty().into()));
100        }
101    }
102
103    if event_size > config.max_event_size() {
104        return Err(DiscardItemType::Event);
105    }
106    if attachments_size > config.max_attachments_size() {
107        return Err(DiscardItemType::Attachment(
108            DiscardAttachmentType::Attachment,
109        ));
110    }
111    if session_count > config.max_session_count() {
112        return Err(DiscardItemType::Session);
113    }
114    if span_count > config.max_span_count() {
115        return Err(DiscardItemType::Span);
116    }
117    if log_count > config.max_log_count() {
118        return Err(DiscardItemType::Log);
119    }
120    if client_reports_size > config.max_client_reports_size() {
121        return Err(DiscardItemType::ClientReport);
122    }
123
124    Ok(())
125}
126
127/// Checks for valid envelope items.
128///
129/// If Relay is configured to drop unknown items, this function removes them from the Envelope. All
130/// known items will be retained.
131pub fn remove_unknown_items(config: &Config, envelope: &mut ManagedEnvelope) {
132    if !config.accept_unknown_items() {
133        envelope.retain_items(|item| match item.ty() {
134            ItemType::Unknown(ty) => {
135                relay_log::debug!("dropping unknown item of type '{ty}'");
136                ItemAction::DropSilently
137            }
138            _ => match item.attachment_type() {
139                Some(AttachmentType::Unknown(ty)) => {
140                    relay_log::debug!("dropping unknown attachment of type '{ty}'");
141                    ItemAction::DropSilently
142                }
143                _ => ItemAction::Keep,
144            },
145        });
146    }
147}