Backed out 5 changesets (bug 1890092, bug 1888683) for causing build bustages & crash...
[gecko.git] / third_party / rust / glean-core / src / error_recording.rs
blobaaf850d019eec0c8860e80fe6f041ae564f9fb0d
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
5 //! # Error Recording
6 //!
7 //! Glean keeps track of errors that occured due to invalid labels or invalid values when recording
8 //! other metrics.
9 //!
10 //! Error counts are stored in labeled counters in the `glean.error` category.
11 //! The labeled counter metrics that store the errors are defined in the `metrics.yaml` for documentation purposes,
12 //! but are not actually used directly, since the `send_in_pings` value needs to match the pings of the metric that is erroring (plus the "metrics" ping),
13 //! not some constant value that we could define in `metrics.yaml`.
15 use std::convert::TryFrom;
16 use std::fmt::Display;
18 use crate::common_metric_data::CommonMetricDataInternal;
19 use crate::error::{Error, ErrorKind};
20 use crate::metrics::labeled::{combine_base_identifier_and_label, strip_label};
21 use crate::metrics::CounterMetric;
22 use crate::CommonMetricData;
23 use crate::Glean;
24 use crate::Lifetime;
26 /// The possible error types for metric recording.
27 /// Note: the cases in this enum must be kept in sync with the ones
28 /// in the platform-specific code (e.g. `ErrorType.kt`) and with the
29 /// metrics in the registry files.
30 // When adding a new error type ensure it's also added to `ErrorType::iter()` below.
31 #[repr(C)]
32 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
33 pub enum ErrorType {
34     /// For when the value to be recorded does not match the metric-specific restrictions
35     InvalidValue,
36     /// For when the label of a labeled metric does not match the restrictions
37     InvalidLabel,
38     /// For when the metric caught an invalid state while recording
39     InvalidState,
40     /// For when the value to be recorded overflows the metric-specific upper range
41     InvalidOverflow,
44 impl ErrorType {
45     /// The error type's metric id
46     pub fn as_str(&self) -> &'static str {
47         match self {
48             ErrorType::InvalidValue => "invalid_value",
49             ErrorType::InvalidLabel => "invalid_label",
50             ErrorType::InvalidState => "invalid_state",
51             ErrorType::InvalidOverflow => "invalid_overflow",
52         }
53     }
55     /// Return an iterator over all possible error types.
56     ///
57     /// ```
58     /// # use glean_core::ErrorType;
59     /// let errors = ErrorType::iter();
60     /// let all_errors = errors.collect::<Vec<_>>();
61     /// assert_eq!(4, all_errors.len());
62     /// ```
63     pub fn iter() -> impl Iterator<Item = Self> {
64         // N.B.: This has no compile-time guarantees that it is complete.
65         // New `ErrorType` variants will need to be added manually.
66         [
67             ErrorType::InvalidValue,
68             ErrorType::InvalidLabel,
69             ErrorType::InvalidState,
70             ErrorType::InvalidOverflow,
71         ]
72         .iter()
73         .copied()
74     }
77 impl TryFrom<i32> for ErrorType {
78     type Error = Error;
80     fn try_from(value: i32) -> Result<ErrorType, Self::Error> {
81         match value {
82             0 => Ok(ErrorType::InvalidValue),
83             1 => Ok(ErrorType::InvalidLabel),
84             2 => Ok(ErrorType::InvalidState),
85             3 => Ok(ErrorType::InvalidOverflow),
86             e => Err(ErrorKind::Lifetime(e).into()),
87         }
88     }
91 /// For a given metric, get the metric in which to record errors
92 fn get_error_metric_for_metric(meta: &CommonMetricDataInternal, error: ErrorType) -> CounterMetric {
93     // Can't use meta.identifier here, since that might cause infinite recursion
94     // if the label on this metric needs to report an error.
95     let identifier = meta.base_identifier();
96     let name = strip_label(&identifier);
98     // Record errors in the pings the metric is in, as well as the metrics ping.
99     let mut send_in_pings = meta.inner.send_in_pings.clone();
100     let ping_name = "metrics".to_string();
101     if !send_in_pings.contains(&ping_name) {
102         send_in_pings.push(ping_name);
103     }
105     CounterMetric::new(CommonMetricData {
106         name: combine_base_identifier_and_label(error.as_str(), name),
107         category: "glean.error".into(),
108         lifetime: Lifetime::Ping,
109         send_in_pings,
110         ..Default::default()
111     })
114 /// Records an error into Glean.
116 /// Errors are recorded as labeled counters in the `glean.error` category.
118 /// *Note*: We do make assumptions here how labeled metrics are encoded, namely by having the name
119 /// `<name>/<label>`.
120 /// Errors do not adhere to the usual "maximum label" restriction.
122 /// # Arguments
124 /// * `glean` - The Glean instance containing the database
125 /// * `meta` - The metric's meta data
126 /// * `error` -  The error type to record
127 /// * `message` - The message to log. This message is not sent with the ping.
128 ///             It does not need to include the metric id, as that is automatically prepended to the message.
129 /// * `num_errors` - The number of errors of the same type to report.
130 pub fn record_error<O: Into<Option<i32>>>(
131     glean: &Glean,
132     meta: &CommonMetricDataInternal,
133     error: ErrorType,
134     message: impl Display,
135     num_errors: O,
136 ) {
137     let metric = get_error_metric_for_metric(meta, error);
139     log::warn!("{}: {}", meta.base_identifier(), message);
140     let to_report = num_errors.into().unwrap_or(1);
141     debug_assert!(to_report > 0);
142     metric.add_sync(glean, to_report);
145 /// Gets the number of recorded errors for the given metric and error type.
147 /// *Notes: This is a **test-only** API, but we need to expose it to be used in integration tests.
149 /// # Arguments
151 /// * `glean` - The Glean object holding the database
152 /// * `meta` - The metadata of the metric instance
153 /// * `error` - The type of error
155 /// # Returns
157 /// The number of errors reported.
158 pub fn test_get_num_recorded_errors(
159     glean: &Glean,
160     meta: &CommonMetricDataInternal,
161     error: ErrorType,
162 ) -> Result<i32, String> {
163     let metric = get_error_metric_for_metric(meta, error);
165     metric.get_value(glean, Some("metrics")).ok_or_else(|| {
166         format!(
167             "No error recorded for {} in 'metrics' store",
168             meta.base_identifier(),
169         )
170     })
173 #[cfg(test)]
174 mod test {
175     use super::*;
176     use crate::metrics::*;
177     use crate::tests::new_glean;
179     #[test]
180     fn error_type_i32_mapping() {
181         let error: ErrorType = std::convert::TryFrom::try_from(0).unwrap();
182         assert_eq!(error, ErrorType::InvalidValue);
183         let error: ErrorType = std::convert::TryFrom::try_from(1).unwrap();
184         assert_eq!(error, ErrorType::InvalidLabel);
185         let error: ErrorType = std::convert::TryFrom::try_from(2).unwrap();
186         assert_eq!(error, ErrorType::InvalidState);
187         let error: ErrorType = std::convert::TryFrom::try_from(3).unwrap();
188         assert_eq!(error, ErrorType::InvalidOverflow);
189     }
191     #[test]
192     fn recording_of_all_error_types() {
193         let (glean, _t) = new_glean(None);
195         let string_metric = StringMetric::new(CommonMetricData {
196             name: "string_metric".into(),
197             category: "telemetry".into(),
198             send_in_pings: vec!["store1".into(), "store2".into()],
199             disabled: false,
200             lifetime: Lifetime::User,
201             ..Default::default()
202         });
204         let expected_invalid_values_errors: i32 = 1;
205         let expected_invalid_labels_errors: i32 = 2;
207         record_error(
208             &glean,
209             string_metric.meta(),
210             ErrorType::InvalidValue,
211             "Invalid value",
212             None,
213         );
215         record_error(
216             &glean,
217             string_metric.meta(),
218             ErrorType::InvalidLabel,
219             "Invalid label",
220             expected_invalid_labels_errors,
221         );
223         let invalid_val =
224             get_error_metric_for_metric(string_metric.meta(), ErrorType::InvalidValue);
225         let invalid_label =
226             get_error_metric_for_metric(string_metric.meta(), ErrorType::InvalidLabel);
227         for &store in &["store1", "store2", "metrics"] {
228             assert_eq!(
229                 Some(expected_invalid_values_errors),
230                 invalid_val.get_value(&glean, Some(store))
231             );
233             assert_eq!(
234                 Some(expected_invalid_labels_errors),
235                 invalid_label.get_value(&glean, Some(store))
236             );
237         }
238     }