glean_core/
error_recording.rs1use std::fmt::Display;
16
17use crate::common_metric_data::CommonMetricDataInternal;
18use crate::error::{Error, ErrorKind};
19use crate::metrics::labeled::{combine_base_identifier_and_label, strip_label};
20use crate::metrics::CounterMetric;
21use crate::CommonMetricData;
22use crate::Glean;
23use crate::Lifetime;
24
25#[repr(C)]
32#[derive(Copy, Clone, Debug, PartialEq, Eq)]
33pub enum ErrorType {
34 InvalidValue,
36 InvalidLabel,
38 InvalidState,
40 InvalidOverflow,
42}
43
44impl ErrorType {
45 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 }
54
55 pub fn iter() -> impl Iterator<Item = Self> {
64 [
67 ErrorType::InvalidValue,
68 ErrorType::InvalidLabel,
69 ErrorType::InvalidState,
70 ErrorType::InvalidOverflow,
71 ]
72 .iter()
73 .copied()
74 }
75}
76
77impl TryFrom<i32> for ErrorType {
78 type Error = Error;
79
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 }
89}
90
91fn get_error_metric_for_metric(meta: &CommonMetricDataInternal, error: ErrorType) -> CounterMetric {
93 let identifier = meta.base_identifier();
96 let name = strip_label(&identifier);
97
98 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 }
104
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 })
112}
113
114pub 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);
138
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);
143}
144
145pub 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);
164
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 })
171}
172
173#[cfg(test)]
174mod test {
175 use super::*;
176 use crate::metrics::*;
177 use crate::tests::new_glean;
178
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 }
190
191 #[test]
192 fn recording_of_all_error_types() {
193 let (glean, _t) = new_glean(None);
194
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 });
203
204 let expected_invalid_values_errors: i32 = 1;
205 let expected_invalid_labels_errors: i32 = 2;
206
207 record_error(
208 &glean,
209 string_metric.meta(),
210 ErrorType::InvalidValue,
211 "Invalid value",
212 None,
213 );
214
215 record_error(
216 &glean,
217 string_metric.meta(),
218 ErrorType::InvalidLabel,
219 "Invalid label",
220 expected_invalid_labels_errors,
221 );
222
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 );
232
233 assert_eq!(
234 Some(expected_invalid_labels_errors),
235 invalid_label.get_value(&glean, Some(store))
236 );
237 }
238 }
239}