relay_event_schema/protocol/contexts/
mod.rs

1mod app;
2mod browser;
3mod chromium_stability_report;
4mod cloud_resource;
5mod culture;
6mod device;
7mod flags;
8mod gpu;
9mod memory_info;
10mod monitor;
11mod nel;
12mod os;
13mod ota_updates;
14mod otel;
15mod performance_score;
16mod profile;
17mod replay;
18mod reprocessing;
19mod response;
20mod runtime;
21mod spring;
22mod threadpool_info;
23mod trace;
24mod unity;
25mod user_report_v2;
26pub use app::*;
27pub use browser::*;
28pub use chromium_stability_report::*;
29pub use cloud_resource::*;
30pub use culture::*;
31pub use device::*;
32pub use gpu::*;
33pub use memory_info::*;
34pub use monitor::*;
35pub use nel::*;
36pub use os::*;
37pub use ota_updates::*;
38pub use otel::*;
39pub use performance_score::*;
40pub use profile::*;
41pub use replay::*;
42pub use reprocessing::*;
43pub use response::*;
44pub use runtime::*;
45pub use spring::*;
46pub use threadpool_info::*;
47pub use trace::*;
48pub use unity::*;
49pub use user_report_v2::*;
50
51use relay_protocol::{Annotated, Empty, FromValue, IntoValue, Object, Value};
52
53use crate::processor::ProcessValue;
54
55/// A span's operation type.
56///
57/// Tries to follow OpenCensus/OpenTracing's span types. Examples are `db.statement` for database
58/// queries or `http` for external HTTP calls.
59pub type OperationType = String;
60
61/// Origin type such as `auto.http`.
62/// Follows the pattern described in the [develop docs](https://develop.sentry.dev/sdk/performance/trace-origin/).
63pub type OriginType = String;
64
65/// A context describes environment info (e.g. device, os or browser).
66#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
67#[metastructure(process_func = "process_context")]
68pub enum Context {
69    /// Device information.
70    Device(Box<DeviceContext>),
71    /// Operating system information.
72    Os(Box<OsContext>),
73    /// Runtime information.
74    Runtime(Box<RuntimeContext>),
75    /// Application information.
76    App(Box<AppContext>),
77    /// Web browser information.
78    Browser(Box<BrowserContext>),
79    /// Information about device's GPU.
80    Gpu(Box<GpuContext>),
81    /// Information related to Tracing.
82    Trace(Box<TraceContext>),
83    /// Information related to Profiling.
84    Profile(Box<ProfileContext>),
85    /// Information related to Replay.
86    Replay(Box<ReplayContext>),
87    /// Information related to Feature flags.
88    Flags(Box<flags::FlagsContext>),
89    /// Information related to User Report V2. TODO:(jferg): rename to UserFeedbackContext
90    #[metastructure(tag = "feedback")]
91    UserReportV2(Box<UserReportV2Context>),
92    /// Information related to Memory usage and garbage collection metrics.
93    #[metastructure(tag = "memory_info")]
94    MemoryInfo(Box<MemoryInfoContext>),
95    /// Information related to Monitors feature.
96    Monitor(Box<MonitorContext>),
97    /// Auxilliary information for reprocessing.
98    #[metastructure(omit_from_schema)]
99    Reprocessing(Box<ReprocessingContext>),
100    /// Response information.
101    Response(Box<ResponseContext>),
102    /// OpenTelemetry information.
103    Otel(Box<OtelContext>),
104    /// Cloud resource information.
105    CloudResource(Box<CloudResourceContext>),
106    /// Culture information.
107    Culture(Box<CultureContext>),
108    /// Nel information.
109    Nel(Box<NelContext>),
110    /// Performance score information.
111    PerformanceScore(Box<PerformanceScoreContext>),
112    /// Spring / Spring Boot information.
113    Spring(Box<SpringContext>),
114    /// Thread pool information.
115    #[metastructure(tag = "threadpool_info")]
116    ThreadPoolInfo(Box<ThreadPoolInfoContext>),
117    /// OTA Updates information.
118    OTAUpdates(Box<OTAUpdatesContext>),
119    /// Chromium Stability Report from minidump.
120    ChromiumStabilityReport(Box<StabilityReportContext>),
121    /// Unity information.
122    Unity(Box<UnityContext>),
123    /// Additional arbitrary fields for forwards compatibility.
124    #[metastructure(fallback_variant)]
125    Other(#[metastructure(pii = "true")] Object<Value>),
126}
127
128#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
129pub struct ContextInner(#[metastructure(max_depth = 7, max_bytes = 8192)] pub Context);
130
131impl From<Context> for ContextInner {
132    fn from(c: Context) -> ContextInner {
133        ContextInner(c)
134    }
135}
136
137/// The Contexts interface provides additional context data. Typically, this is data related to the
138/// current user and the environment. For example, the device or application version. Its canonical
139/// name is `contexts`.
140///
141/// The `contexts` type can be used to define arbitrary contextual data on the event. It accepts an
142/// object of key/value pairs. The key is the “alias” of the context and can be freely chosen.
143/// However, as per policy, it should match the type of the context unless there are two values for
144/// a type. You can omit `type` if the key name is the type.
145///
146/// Unknown data for the contexts is rendered as a key/value list.
147///
148/// For more details about sending additional data with your event, see the [full documentation on
149/// Additional Data](https://docs.sentry.io/enriching-error-data/additional-data/).
150#[derive(Clone, Debug, PartialEq, Empty, IntoValue, ProcessValue, Default)]
151#[metastructure(process_func = "process_contexts")]
152pub struct Contexts(pub Object<ContextInner>);
153
154impl Contexts {
155    /// Creates an empty contexts map.
156    pub fn new() -> Contexts {
157        Contexts(Object::new())
158    }
159
160    /// Inserts a context under the default key for the context.
161    pub fn add<C>(&mut self, context: C)
162    where
163        C: DefaultContext,
164    {
165        self.insert(C::default_key().to_owned(), context.into_context());
166    }
167
168    /// Inserts a context under a custom given key.
169    ///
170    /// By convention, every typed context has a default key. Use [`add`](Self::add) to insert such
171    /// contexts, instead.
172    pub fn insert(&mut self, key: String, context: Context) {
173        self.0.insert(key, Annotated::new(ContextInner(context)));
174    }
175
176    /// Returns `true` if a matching context resides in the map at its default key.
177    pub fn contains<C>(&self) -> bool
178    where
179        C: DefaultContext,
180    {
181        // Use `get` to perform a type check.
182        self.get::<C>().is_some()
183    }
184
185    /// Returns `true` if a context with the provided key is present in the map.
186    ///
187    /// By convention, every typed context has a default key. Use [`contains`](Self::contains) to
188    /// check such contexts, instead.
189    pub fn contains_key<S>(&self, key: S) -> bool
190    where
191        S: AsRef<str>,
192    {
193        self.0.contains_key(key.as_ref())
194    }
195
196    /// Returns the context at its default key or constructs it if not present.
197    pub fn get_or_default<C>(&mut self) -> &mut C
198    where
199        C: DefaultContext,
200    {
201        if !self.contains::<C>() {
202            self.add(C::default());
203        }
204
205        self.get_mut().unwrap()
206    }
207
208    /// Returns the context at the specified key or constructs it if not present.
209    ///
210    /// By convention, every typed context has a default key. Use
211    /// [`get_or_default`](Self::get_or_default) to insert such contexts, instead.
212    pub fn get_or_insert_with<F, S>(&mut self, key: S, context_builder: F) -> &mut Context
213    where
214        F: FnOnce() -> Context,
215        S: Into<String>,
216    {
217        &mut self
218            .0
219            .entry(key.into())
220            .or_insert_with(Annotated::empty)
221            .value_mut()
222            .get_or_insert_with(|| ContextInner(context_builder()))
223            .0
224    }
225
226    /// Returns a reference to the default context by type.
227    pub fn get<C>(&self) -> Option<&C>
228    where
229        C: DefaultContext,
230    {
231        C::cast(self.get_key(C::default_key())?)
232    }
233
234    /// Returns a mutable reference to the default context by type.
235    pub fn get_mut<C>(&mut self) -> Option<&mut C>
236    where
237        C: DefaultContext,
238    {
239        C::cast_mut(self.get_key_mut(C::default_key())?)
240    }
241
242    /// Returns a reference to the context specified by `key`.
243    ///
244    /// By convention, every typed context has a default key. Use [`get`](Self::get) to retrieve
245    /// such contexts, instead.
246    pub fn get_key<S>(&self, key: S) -> Option<&Context>
247    where
248        S: AsRef<str>,
249    {
250        Some(&self.0.get(key.as_ref())?.value().as_ref()?.0)
251    }
252
253    /// Returns a mutable reference to the context specified by `key`.
254    ///
255    /// By convention, every typed context has a default key. Use [`get_mut`](Self::get_mut) to
256    /// retrieve such contexts, instead.
257    pub fn get_key_mut<S>(&mut self, key: S) -> Option<&mut Context>
258    where
259        S: AsRef<str>,
260    {
261        Some(&mut self.0.get_mut(key.as_ref())?.value_mut().as_mut()?.0)
262    }
263
264    /// Removes a context from the map, returning the context it was previously in the map.
265    ///
266    /// Returns `Some` if a matching context was removed from the default key. If the context at the
267    /// default key does not have a matching type, it is removed but `None` is returned.
268    pub fn remove<C>(&mut self) -> Option<C>
269    where
270        C: DefaultContext,
271    {
272        let context = self.remove_key(C::default_key())?;
273        C::from_context(context)
274    }
275
276    /// Removes a context from the map, returning the context it was previously in the map.
277    ///
278    /// By convention, every typed context has a default key. Use [`remove`](Self::remove) to
279    /// retrieve such contexts, instead.
280    pub fn remove_key<S>(&mut self, key: S) -> Option<Context>
281    where
282        S: AsRef<str>,
283    {
284        let inner = self.0.remove(key.as_ref())?;
285        Some(inner.into_value()?.0)
286    }
287}
288
289impl FromValue for Contexts {
290    fn from_value(mut annotated: Annotated<Value>) -> Annotated<Self> {
291        if let Annotated(Some(Value::Object(ref mut items)), _) = annotated {
292            for (key, value) in items.iter_mut() {
293                if let Annotated(Some(Value::Object(items)), _) = value {
294                    // Set the `"type"` if it's empty and overwrite it if it's an empty object or array.
295                    if !is_valid_context_type(items.get("type")) {
296                        items.insert(
297                            "type".to_owned(),
298                            Annotated::new(Value::String(key.to_string())),
299                        );
300                    }
301                }
302            }
303        }
304        FromValue::from_value(annotated).map_value(Contexts)
305    }
306}
307
308/// Returns `true` if `value` is a non-empty string, which is the only valid value
309/// for the `"type"` field of a context.
310fn is_valid_context_type(value: Option<&Annotated<Value>>) -> bool {
311    matches!(value.and_then(|v| v.value()), Some(Value::String(s)) if !s.is_empty())
312}
313
314/// A well-known context in the [`Contexts`] interface.
315///
316/// These contexts have a [default key](Self::default_key) in the contexts map and can be
317/// constructed as an empty default value.
318pub trait DefaultContext: Default {
319    /// The default key at which this context resides in [`Contexts`].
320    fn default_key() -> &'static str;
321
322    /// Converts this context type from a generic context type.
323    ///
324    /// Returns `Some` if the context is of this type. Otherwise, returns `None`.
325    fn from_context(context: Context) -> Option<Self>;
326
327    /// Casts a reference to this context type from a generic context type.
328    ///
329    /// Returns `Some` if the context is of this type. Otherwise, returns `None`.
330    fn cast(context: &Context) -> Option<&Self>;
331
332    /// Casts a mutable reference to this context type from a generic context type.
333    ///
334    /// Returns `Some` if the context is of this type. Otherwise, returns `None`.
335    fn cast_mut(context: &mut Context) -> Option<&mut Self>;
336
337    /// Boxes this context type in the generic context wrapper.
338    ///
339    /// Returns `Some` if the context is of this type. Otherwise, returns `None`.
340    fn into_context(self) -> Context;
341}
342
343#[cfg(test)]
344mod tests {
345    use relay_protocol::{Map, Meta};
346
347    use super::*;
348    use crate::processor::{ProcessingResult, ProcessingState, Processor};
349    use crate::protocol::Event;
350
351    #[test]
352    fn test_other_context_roundtrip() {
353        let json = r#"{"other":"value","type":"mytype"}"#;
354        let context = Annotated::new(Context::Other({
355            let mut map = Map::new();
356            map.insert(
357                "other".to_owned(),
358                Annotated::new(Value::String("value".to_owned())),
359            );
360            map.insert(
361                "type".to_owned(),
362                Annotated::new(Value::String("mytype".to_owned())),
363            );
364            map
365        }));
366
367        assert_eq!(context, Annotated::from_json(json).unwrap());
368        assert_eq!(json, context.to_json().unwrap());
369    }
370
371    #[test]
372    fn test_untagged_context_deserialize() {
373        let json = r#"{"os": {"name": "Linux"}}"#;
374
375        let mut map = Contexts::new();
376        map.add(OsContext {
377            name: Annotated::new("Linux".to_owned()),
378            ..Default::default()
379        });
380
381        assert_eq!(Annotated::new(map), Annotated::from_json(json).unwrap());
382    }
383
384    #[test]
385    fn test_context_invalid_type_deserialize() {
386        let json = r#"{
387            "monitor":{"name":"Foobar","type":17},
388            "os":{"name":"Linux","type":{}},
389            "profile":{"profile_id":"52df9022835246eeb317dbd739ccd059","type":""},
390            "runtime":{"name":"rustc","type":["invalid"]}
391        }"#;
392
393        let mut map = Contexts::new();
394        map.add(MonitorContext(
395            [("name".to_owned(), Value::String("Foobar".to_owned()).into())]
396                .into_iter()
397                .collect(),
398        ));
399        map.add(OsContext {
400            name: Annotated::new("Linux".to_owned()),
401            ..Default::default()
402        });
403        map.add(ProfileContext {
404            profile_id: Annotated::new("52df9022835246eeb317dbd739ccd059".parse().unwrap()),
405            ..Default::default()
406        });
407        map.add(RuntimeContext {
408            name: Annotated::new("rustc".to_owned()),
409            ..Default::default()
410        });
411
412        assert_eq!(Annotated::new(map), Annotated::from_json(json).unwrap());
413    }
414
415    #[test]
416    fn test_multiple_contexts_roundtrip() {
417        let json =
418            r#"{"os":{"name":"Linux","type":"os"},"runtime":{"name":"rustc","type":"runtime"}}"#;
419
420        let mut map = Contexts::new();
421        map.add(OsContext {
422            name: Annotated::new("Linux".to_owned()),
423            ..Default::default()
424        });
425        map.add(RuntimeContext {
426            name: Annotated::new("rustc".to_owned()),
427            ..Default::default()
428        });
429
430        let contexts = Annotated::new(map);
431        assert_eq!(contexts, Annotated::from_json(json).unwrap());
432        assert_eq!(json, contexts.to_json().unwrap());
433    }
434
435    #[test]
436    fn test_context_processing() {
437        let mut event = Annotated::new(Event {
438            contexts: {
439                let mut contexts = Contexts::new();
440                contexts.add(RuntimeContext {
441                    name: Annotated::new("php".to_owned()),
442                    version: Annotated::new("7.1.20-1+ubuntu16.04.1+deb.sury.org+1".to_owned()),
443                    ..Default::default()
444                });
445                Annotated::new(contexts)
446            },
447            ..Default::default()
448        });
449
450        struct FooProcessor {
451            called: bool,
452        }
453
454        impl Processor for FooProcessor {
455            #[inline]
456            fn process_context(
457                &mut self,
458                _value: &mut Context,
459                _meta: &mut Meta,
460                _state: &ProcessingState<'_>,
461            ) -> ProcessingResult {
462                self.called = true;
463                Ok(())
464            }
465        }
466
467        let mut processor = FooProcessor { called: false };
468        crate::processor::process_value(&mut event, &mut processor, ProcessingState::root())
469            .unwrap();
470        assert!(processor.called);
471    }
472}