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 | ItemType::OtelLog => {
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::ProfileChunk => config.max_profile_size(),
87            ItemType::Unknown(_) => NO_LIMIT,
88        };
89
90        // For item containers, we want to check that the contained items obey
91        // the size limits *on average*.
92        // For standalone items, this is just the item size itself.
93        let avg_item_size = item.len() / (item.item_count().unwrap_or(1).max(1) as usize);
94        if avg_item_size > max_size {
95            return Err(item
96                .attachment_type()
97                .map(|t| t.into())
98                .unwrap_or_else(|| item.ty().into()));
99        }
100    }
101
102    if event_size > config.max_event_size() {
103        return Err(DiscardItemType::Event);
104    }
105    if attachments_size > config.max_attachments_size() {
106        return Err(DiscardItemType::Attachment(
107            DiscardAttachmentType::Attachment,
108        ));
109    }
110    if session_count > config.max_session_count() {
111        return Err(DiscardItemType::Session);
112    }
113    if span_count > config.max_span_count() {
114        return Err(DiscardItemType::Span);
115    }
116    if log_count > config.max_log_count() {
117        return Err(DiscardItemType::Log);
118    }
119    if client_reports_size > config.max_client_reports_size() {
120        return Err(DiscardItemType::ClientReport);
121    }
122
123    Ok(())
124}
125
126/// Checks for valid envelope items.
127///
128/// If Relay is configured to drop unknown items, this function removes them from the Envelope. All
129/// known items will be retained.
130pub fn remove_unknown_items(config: &Config, envelope: &mut ManagedEnvelope) {
131    if !config.accept_unknown_items() {
132        envelope.retain_items(|item| match item.ty() {
133            ItemType::Unknown(ty) => {
134                relay_log::debug!("dropping unknown item of type '{ty}'");
135                ItemAction::DropSilently
136            }
137            _ => match item.attachment_type() {
138                Some(AttachmentType::Unknown(ty)) => {
139                    relay_log::debug!("dropping unknown attachment of type '{ty}'");
140                    ItemAction::DropSilently
141                }
142                _ => ItemAction::Keep,
143            },
144        });
145    }
146}