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/.
7 //! Glean keeps track of errors that occured due to invalid labels or invalid values when recording
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;
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.
32 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
34 /// For when the value to be recorded does not match the metric-specific restrictions
36 /// For when the label of a labeled metric does not match the restrictions
38 /// For when the metric caught an invalid state while recording
40 /// For when the value to be recorded overflows the metric-specific upper range
45 /// The error type's metric id
46 pub fn as_str(&self) -> &'static str {
48 ErrorType::InvalidValue => "invalid_value",
49 ErrorType::InvalidLabel => "invalid_label",
50 ErrorType::InvalidState => "invalid_state",
51 ErrorType::InvalidOverflow => "invalid_overflow",
55 /// Return an iterator over all possible error types.
58 /// # use glean_core::ErrorType;
59 /// let errors = ErrorType::iter();
60 /// let all_errors = errors.collect::<Vec<_>>();
61 /// assert_eq!(4, all_errors.len());
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.
67 ErrorType::InvalidValue,
68 ErrorType::InvalidLabel,
69 ErrorType::InvalidState,
70 ErrorType::InvalidOverflow,
77 impl TryFrom<i32> for ErrorType {
80 fn try_from(value: i32) -> Result<ErrorType, Self::Error> {
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()),
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);
105 CounterMetric::new(CommonMetricData {
106 name: combine_base_identifier_and_label(error.as_str(), name),
107 category: "glean.error".into(),
108 lifetime: Lifetime::Ping,
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.
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>>>(
132 meta: &CommonMetricDataInternal,
134 message: impl Display,
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.
151 /// * `glean` - The Glean object holding the database
152 /// * `meta` - The metadata of the metric instance
153 /// * `error` - The type of error
157 /// The number of errors reported.
158 pub fn test_get_num_recorded_errors(
160 meta: &CommonMetricDataInternal,
162 ) -> Result<i32, String> {
163 let metric = get_error_metric_for_metric(meta, error);
165 metric.get_value(glean, Some("metrics")).ok_or_else(|| {
167 "No error recorded for {} in 'metrics' store",
168 meta.base_identifier(),
176 use crate::metrics::*;
177 use crate::tests::new_glean;
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);
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()],
200 lifetime: Lifetime::User,
204 let expected_invalid_values_errors: i32 = 1;
205 let expected_invalid_labels_errors: i32 = 2;
209 string_metric.meta(),
210 ErrorType::InvalidValue,
217 string_metric.meta(),
218 ErrorType::InvalidLabel,
220 expected_invalid_labels_errors,
224 get_error_metric_for_metric(string_metric.meta(), ErrorType::InvalidValue);
226 get_error_metric_for_metric(string_metric.meta(), ErrorType::InvalidLabel);
227 for &store in &["store1", "store2", "metrics"] {
229 Some(expected_invalid_values_errors),
230 invalid_val.get_value(&glean, Some(store))
234 Some(expected_invalid_labels_errors),
235 invalid_label.get_value(&glean, Some(store))