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/.
4
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`.
14
15 use std::convert::TryFrom;
16 use std::fmt::Display;
17
18 use crate::error::{Error, ErrorKind};
19 use crate::metrics::labeled::{combine_base_identifier_and_label, strip_label};
20 use crate::metrics::CounterMetric;
21 use crate::CommonMetricData;
22 use crate::Glean;
23 use crate::Lifetime;
24
25 /// The possible error types for metric recording.
26 /// Note: the cases in this enum must be kept in sync with the ones
27 /// in the platform-specific code (e.g. `ErrorType.kt`) and with the
28 /// metrics in the registry files.
29 // When adding a new error type ensure it's also added to `ErrorType::iter()` below.
30 #[derive(Copy, Clone, Debug, PartialEq)]
31 pub enum ErrorType {
32 /// For when the value to be recorded does not match the metric-specific restrictions
33 InvalidValue,
34 /// For when the label of a labeled metric does not match the restrictions
35 InvalidLabel,
36 /// For when the metric caught an invalid state while recording
37 InvalidState,
38 /// For when the value to be recorded overflows the metric-specific upper range
39 InvalidOverflow,
40 }
41
42 impl ErrorType {
43 /// The error type's metric id
as_str(&self) -> &'static str44 pub fn as_str(&self) -> &'static str {
45 match self {
46 ErrorType::InvalidValue => "invalid_value",
47 ErrorType::InvalidLabel => "invalid_label",
48 ErrorType::InvalidState => "invalid_state",
49 ErrorType::InvalidOverflow => "invalid_overflow",
50 }
51 }
52
53 /// Return an iterator over all possible error types.
54 ///
55 /// ```
56 /// # use glean_core::ErrorType;
57 /// let errors = ErrorType::iter();
58 /// let all_errors = errors.collect::<Vec<_>>();
59 /// assert_eq!(4, all_errors.len());
60 /// ```
iter() -> impl Iterator<Item = Self>61 pub fn iter() -> impl Iterator<Item = Self> {
62 // N.B.: This has no compile-time guarantees that it is complete.
63 // New `ErrorType` variants will need to be added manually.
64 [
65 ErrorType::InvalidValue,
66 ErrorType::InvalidLabel,
67 ErrorType::InvalidState,
68 ErrorType::InvalidOverflow,
69 ]
70 .iter()
71 .copied()
72 }
73 }
74
75 impl TryFrom<i32> for ErrorType {
76 type Error = Error;
77
try_from(value: i32) -> Result<ErrorType, Self::Error>78 fn try_from(value: i32) -> Result<ErrorType, Self::Error> {
79 match value {
80 0 => Ok(ErrorType::InvalidValue),
81 1 => Ok(ErrorType::InvalidLabel),
82 2 => Ok(ErrorType::InvalidState),
83 3 => Ok(ErrorType::InvalidOverflow),
84 e => Err(ErrorKind::Lifetime(e).into()),
85 }
86 }
87 }
88
89 /// For a given metric, get the metric in which to record errors
get_error_metric_for_metric(meta: &CommonMetricData, error: ErrorType) -> CounterMetric90 fn get_error_metric_for_metric(meta: &CommonMetricData, error: ErrorType) -> CounterMetric {
91 // Can't use meta.identifier here, since that might cause infinite recursion
92 // if the label on this metric needs to report an error.
93 let identifier = meta.base_identifier();
94 let name = strip_label(&identifier);
95
96 // Record errors in the pings the metric is in, as well as the metrics ping.
97 let mut send_in_pings = meta.send_in_pings.clone();
98 let ping_name = "metrics".to_string();
99 if !send_in_pings.contains(&ping_name) {
100 send_in_pings.push(ping_name);
101 }
102
103 CounterMetric::new(CommonMetricData {
104 name: combine_base_identifier_and_label(error.as_str(), name),
105 category: "glean.error".into(),
106 lifetime: Lifetime::Ping,
107 send_in_pings,
108 ..Default::default()
109 })
110 }
111
112 /// Records an error into Glean.
113 ///
114 /// Errors are recorded as labeled counters in the `glean.error` category.
115 ///
116 /// *Note*: We do make assumptions here how labeled metrics are encoded, namely by having the name
117 /// `<name>/<label>`.
118 /// Errors do not adhere to the usual "maximum label" restriction.
119 ///
120 /// # Arguments
121 ///
122 /// * `glean` - The Glean instance containing the database
123 /// * `meta` - The metric's meta data
124 /// * `error` - The error type to record
125 /// * `message` - The message to log. This message is not sent with the ping.
126 /// It does not need to include the metric id, as that is automatically prepended to the message.
127 /// * `num_errors` - The number of errors of the same type to report.
record_error<O: Into<Option<i32>>>( glean: &Glean, meta: &CommonMetricData, error: ErrorType, message: impl Display, num_errors: O, )128 pub fn record_error<O: Into<Option<i32>>>(
129 glean: &Glean,
130 meta: &CommonMetricData,
131 error: ErrorType,
132 message: impl Display,
133 num_errors: O,
134 ) {
135 let metric = get_error_metric_for_metric(meta, error);
136
137 log::warn!("{}: {}", meta.base_identifier(), message);
138 let to_report = num_errors.into().unwrap_or(1);
139 debug_assert!(to_report > 0);
140 metric.add(glean, to_report);
141 }
142
143 /// Gets the number of recorded errors for the given metric and error type.
144 ///
145 /// *Notes: This is a **test-only** API, but we need to expose it to be used in integration tests.
146 ///
147 /// # Arguments
148 ///
149 /// * `glean` - The Glean object holding the database
150 /// * `meta` - The metadata of the metric instance
151 /// * `error` - The type of error
152 ///
153 /// # Returns
154 ///
155 /// The number of errors reported.
test_get_num_recorded_errors( glean: &Glean, meta: &CommonMetricData, error: ErrorType, ping_name: Option<&str>, ) -> Result<i32, String>156 pub fn test_get_num_recorded_errors(
157 glean: &Glean,
158 meta: &CommonMetricData,
159 error: ErrorType,
160 ping_name: Option<&str>,
161 ) -> Result<i32, String> {
162 let use_ping_name = ping_name.unwrap_or(&meta.send_in_pings[0]);
163 let metric = get_error_metric_for_metric(meta, error);
164
165 metric.test_get_value(glean, use_ping_name).ok_or_else(|| {
166 format!(
167 "No error recorded for {} in '{}' store",
168 meta.base_identifier(),
169 use_ping_name
170 )
171 })
172 }
173
174 #[cfg(test)]
175 mod test {
176 use super::*;
177 use crate::metrics::*;
178 use crate::tests::new_glean;
179
180 #[test]
error_type_i32_mapping()181 fn error_type_i32_mapping() {
182 let error: ErrorType = std::convert::TryFrom::try_from(0).unwrap();
183 assert_eq!(error, ErrorType::InvalidValue);
184 let error: ErrorType = std::convert::TryFrom::try_from(1).unwrap();
185 assert_eq!(error, ErrorType::InvalidLabel);
186 let error: ErrorType = std::convert::TryFrom::try_from(2).unwrap();
187 assert_eq!(error, ErrorType::InvalidState);
188 let error: ErrorType = std::convert::TryFrom::try_from(3).unwrap();
189 assert_eq!(error, ErrorType::InvalidOverflow);
190 }
191
192 #[test]
recording_of_all_error_types()193 fn recording_of_all_error_types() {
194 let (glean, _t) = new_glean(None);
195
196 let string_metric = StringMetric::new(CommonMetricData {
197 name: "string_metric".into(),
198 category: "telemetry".into(),
199 send_in_pings: vec!["store1".into(), "store2".into()],
200 disabled: false,
201 lifetime: Lifetime::User,
202 ..Default::default()
203 });
204
205 let expected_invalid_values_errors: i32 = 1;
206 let expected_invalid_labels_errors: i32 = 2;
207
208 record_error(
209 &glean,
210 string_metric.meta(),
211 ErrorType::InvalidValue,
212 "Invalid value",
213 None,
214 );
215
216 record_error(
217 &glean,
218 string_metric.meta(),
219 ErrorType::InvalidLabel,
220 "Invalid label",
221 expected_invalid_labels_errors,
222 );
223
224 for store in &["store1", "store2", "metrics"] {
225 assert_eq!(
226 Ok(expected_invalid_values_errors),
227 test_get_num_recorded_errors(
228 &glean,
229 string_metric.meta(),
230 ErrorType::InvalidValue,
231 Some(store)
232 )
233 );
234 assert_eq!(
235 Ok(expected_invalid_labels_errors),
236 test_get_num_recorded_errors(
237 &glean,
238 string_metric.meta(),
239 ErrorType::InvalidLabel,
240 Some(store)
241 )
242 );
243 }
244 }
245 }
246