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