1use std::fmt::Display;
16use std::sync::atomic::AtomicU8;
17
18use rusqlite::Transaction;
19
20use crate::common_metric_data::CommonMetricDataInternal;
21use crate::error::{Error, ErrorKind};
22use crate::metrics::{CounterMetric, Metric};
23use crate::Glean;
24use crate::Lifetime;
25use crate::{CommonMetricData, MetricLabel};
26
27#[repr(C)]
34#[derive(Copy, Clone, Debug, PartialEq, Eq)]
35pub enum ErrorType {
36 InvalidValue,
38 InvalidLabel,
40 InvalidState,
42 InvalidOverflow,
44}
45
46impl ErrorType {
47 pub fn as_str(&self) -> &'static str {
49 match self {
50 ErrorType::InvalidValue => "invalid_value",
51 ErrorType::InvalidLabel => "invalid_label",
52 ErrorType::InvalidState => "invalid_state",
53 ErrorType::InvalidOverflow => "invalid_overflow",
54 }
55 }
56
57 pub fn iter() -> impl Iterator<Item = Self> {
66 [
69 ErrorType::InvalidValue,
70 ErrorType::InvalidLabel,
71 ErrorType::InvalidState,
72 ErrorType::InvalidOverflow,
73 ]
74 .iter()
75 .copied()
76 }
77}
78
79impl TryFrom<i32> for ErrorType {
80 type Error = Error;
81
82 fn try_from(value: i32) -> Result<ErrorType, Self::Error> {
83 match value {
84 0 => Ok(ErrorType::InvalidValue),
85 1 => Ok(ErrorType::InvalidLabel),
86 2 => Ok(ErrorType::InvalidState),
87 3 => Ok(ErrorType::InvalidOverflow),
88 e => Err(ErrorKind::Lifetime(e).into()),
89 }
90 }
91}
92
93fn get_error_metric_for_metric(meta: &CommonMetricDataInternal, error: ErrorType) -> CounterMetric {
95 let name = meta.base_identifier();
98
99 let mut send_in_pings = meta.inner.send_in_pings.clone();
101 let ping_name = "metrics".to_string();
102 if !send_in_pings.contains(&ping_name) {
103 send_in_pings.push(ping_name);
104 }
105 send_in_pings.retain(|elem| elem != "glean_internal_info" && elem != "glean_client_info");
106
107 CounterMetric::new(CommonMetricData {
108 name: error.as_str().to_string(),
109 category: "glean.error".into(),
110 lifetime: Lifetime::Ping,
111 send_in_pings,
112 label: Some(MetricLabel::Label(name.to_string())),
113 ..Default::default()
114 })
115}
116
117pub fn record_error<O: Into<Option<i32>>>(
134 glean: &Glean,
135 meta: &CommonMetricDataInternal,
136 error: ErrorType,
137 message: impl Display,
138 num_errors: O,
139) {
140 let metric = get_error_metric_for_metric(meta, error);
141
142 log::warn!("{}: {}", meta.base_identifier(), message);
143 let to_report = num_errors.into().unwrap_or(1);
144 debug_assert!(to_report > 0);
145 metric.add_sync(glean, to_report);
146}
147
148pub fn record_error_sqlite(
149 glean: &Glean,
150 tx: &mut Transaction,
151 metric_name: &str,
152 send_in_pings: &[String],
153 error: ErrorType,
154 num_errors: i32,
155) {
156 debug_assert!(num_errors > 0);
157 if num_errors <= 0 {
158 log::warn!("Trying to record {num_errors} errors for {metric_name:?} (<= 0). Bailing out.");
159 return;
160 }
161
162 let ping_name = String::from("metrics");
169 let mut send_in_pings = send_in_pings.to_vec();
170 if !send_in_pings.contains(&ping_name) {
171 send_in_pings.push(ping_name);
172 }
173 send_in_pings.retain(|elem| elem != "glean_internal_info" && elem != "glean_client_info");
174
175 let lifetime = Lifetime::Ping;
176 let transform = |old_value| match old_value {
177 Some(Metric::Counter(old_value)) => Metric::Counter(old_value.saturating_add(num_errors)),
178 _ => Metric::Counter(num_errors),
179 };
180
181 let inner = CommonMetricData {
182 category: String::from("glean.error"),
183 name: String::from(error.as_str()),
184 send_in_pings,
185 lifetime,
186 label: Some(MetricLabel::Static(String::from(metric_name))),
187 ..Default::default()
188 };
189 let cmd = CommonMetricDataInternal {
190 inner,
191 disabled: AtomicU8::new(0),
192 };
193 _ = glean
194 .storage()
195 .record_with_transaction(glean, tx, &cmd, transform);
196}
197
198pub fn test_get_num_recorded_errors(
212 glean: &Glean,
213 meta: &CommonMetricDataInternal,
214 error: ErrorType,
215) -> Result<i32, String> {
216 let metric = get_error_metric_for_metric(meta, error);
217
218 metric.get_value(glean, Some("metrics")).ok_or_else(|| {
219 format!(
220 "No error recorded for {} in 'metrics' store",
221 meta.base_identifier(),
222 )
223 })
224}
225
226#[cfg(test)]
227mod test {
228 use super::*;
229 use crate::metrics::*;
230 use crate::tests::new_glean;
231
232 #[test]
233 fn error_type_i32_mapping() {
234 let error: ErrorType = std::convert::TryFrom::try_from(0).unwrap();
235 assert_eq!(error, ErrorType::InvalidValue);
236 let error: ErrorType = std::convert::TryFrom::try_from(1).unwrap();
237 assert_eq!(error, ErrorType::InvalidLabel);
238 let error: ErrorType = std::convert::TryFrom::try_from(2).unwrap();
239 assert_eq!(error, ErrorType::InvalidState);
240 let error: ErrorType = std::convert::TryFrom::try_from(3).unwrap();
241 assert_eq!(error, ErrorType::InvalidOverflow);
242 }
243
244 #[test]
245 fn recording_of_all_error_types() {
246 let (glean, _t) = new_glean(None);
247
248 let string_metric = StringMetric::new(CommonMetricData {
249 name: "string_metric".into(),
250 category: "telemetry".into(),
251 send_in_pings: vec!["store1".into(), "store2".into()],
252 disabled: false,
253 lifetime: Lifetime::User,
254 ..Default::default()
255 });
256
257 let expected_invalid_values_errors: i32 = 1;
258 let expected_invalid_labels_errors: i32 = 2;
259
260 record_error(
261 &glean,
262 string_metric.meta(),
263 ErrorType::InvalidValue,
264 "Invalid value",
265 None,
266 );
267
268 record_error(
269 &glean,
270 string_metric.meta(),
271 ErrorType::InvalidLabel,
272 "Invalid label",
273 expected_invalid_labels_errors,
274 );
275
276 let invalid_val =
277 get_error_metric_for_metric(string_metric.meta(), ErrorType::InvalidValue);
278 let invalid_label =
279 get_error_metric_for_metric(string_metric.meta(), ErrorType::InvalidLabel);
280 for &store in &["store1", "store2", "metrics"] {
281 assert_eq!(
282 Some(expected_invalid_values_errors),
283 invalid_val.get_value(&glean, Some(store))
284 );
285
286 assert_eq!(
287 Some(expected_invalid_labels_errors),
288 invalid_label.get_value(&glean, Some(store))
289 );
290 }
291 }
292}