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
106 CounterMetric::new(CommonMetricData {
107 name: error.as_str().to_string(),
108 category: "glean.error".into(),
109 lifetime: Lifetime::Ping,
110 send_in_pings,
111 label: Some(MetricLabel::Label(name.to_string())),
112 ..Default::default()
113 })
114}
115
116pub fn record_error<O: Into<Option<i32>>>(
133 glean: &Glean,
134 meta: &CommonMetricDataInternal,
135 error: ErrorType,
136 message: impl Display,
137 num_errors: O,
138) {
139 let metric = get_error_metric_for_metric(meta, error);
140
141 log::warn!("{}: {}", meta.base_identifier(), message);
142 let to_report = num_errors.into().unwrap_or(1);
143 debug_assert!(to_report > 0);
144 metric.add_sync(glean, to_report);
145}
146
147pub fn record_error_sqlite(
148 glean: &Glean,
149 tx: &mut Transaction,
150 metric_name: &str,
151 send_in_pings: &[String],
152 error: ErrorType,
153 num_errors: i32,
154) {
155 debug_assert!(num_errors > 0);
156 if num_errors <= 0 {
157 log::warn!("Trying to record {num_errors} errors for {metric_name:?} (<= 0). Bailing out.");
158 return;
159 }
160
161 let ping_name = String::from("metrics");
168 let mut send_in_pings = send_in_pings.to_vec();
169 if !send_in_pings.contains(&ping_name) {
170 send_in_pings.push(ping_name);
171 }
172
173 let lifetime = Lifetime::Ping;
174 let transform = |old_value| match old_value {
175 Some(Metric::Counter(old_value)) => Metric::Counter(old_value.saturating_add(num_errors)),
176 _ => Metric::Counter(num_errors),
177 };
178
179 let inner = CommonMetricData {
180 category: String::from("glean.error"),
181 name: String::from(error.as_str()),
182 send_in_pings,
183 lifetime,
184 label: Some(MetricLabel::Static(String::from(metric_name))),
185 ..Default::default()
186 };
187 let cmd = CommonMetricDataInternal {
188 inner,
189 disabled: AtomicU8::new(0),
190 };
191 _ = glean
192 .storage()
193 .record_with_transaction(glean, tx, &cmd, transform);
194}
195
196pub fn test_get_num_recorded_errors(
210 glean: &Glean,
211 meta: &CommonMetricDataInternal,
212 error: ErrorType,
213) -> Result<i32, String> {
214 let metric = get_error_metric_for_metric(meta, error);
215
216 metric.get_value(glean, Some("metrics")).ok_or_else(|| {
217 format!(
218 "No error recorded for {} in 'metrics' store",
219 meta.base_identifier(),
220 )
221 })
222}
223
224#[cfg(test)]
225mod test {
226 use super::*;
227 use crate::metrics::*;
228 use crate::tests::new_glean;
229
230 #[test]
231 fn error_type_i32_mapping() {
232 let error: ErrorType = std::convert::TryFrom::try_from(0).unwrap();
233 assert_eq!(error, ErrorType::InvalidValue);
234 let error: ErrorType = std::convert::TryFrom::try_from(1).unwrap();
235 assert_eq!(error, ErrorType::InvalidLabel);
236 let error: ErrorType = std::convert::TryFrom::try_from(2).unwrap();
237 assert_eq!(error, ErrorType::InvalidState);
238 let error: ErrorType = std::convert::TryFrom::try_from(3).unwrap();
239 assert_eq!(error, ErrorType::InvalidOverflow);
240 }
241
242 #[test]
243 fn recording_of_all_error_types() {
244 let (glean, _t) = new_glean(None);
245
246 let string_metric = StringMetric::new(CommonMetricData {
247 name: "string_metric".into(),
248 category: "telemetry".into(),
249 send_in_pings: vec!["store1".into(), "store2".into()],
250 disabled: false,
251 lifetime: Lifetime::User,
252 ..Default::default()
253 });
254
255 let expected_invalid_values_errors: i32 = 1;
256 let expected_invalid_labels_errors: i32 = 2;
257
258 record_error(
259 &glean,
260 string_metric.meta(),
261 ErrorType::InvalidValue,
262 "Invalid value",
263 None,
264 );
265
266 record_error(
267 &glean,
268 string_metric.meta(),
269 ErrorType::InvalidLabel,
270 "Invalid label",
271 expected_invalid_labels_errors,
272 );
273
274 let invalid_val =
275 get_error_metric_for_metric(string_metric.meta(), ErrorType::InvalidValue);
276 let invalid_label =
277 get_error_metric_for_metric(string_metric.meta(), ErrorType::InvalidLabel);
278 for &store in &["store1", "store2", "metrics"] {
279 assert_eq!(
280 Some(expected_invalid_values_errors),
281 invalid_val.get_value(&glean, Some(store))
282 );
283
284 assert_eq!(
285 Some(expected_invalid_labels_errors),
286 invalid_label.get_value(&glean, Some(store))
287 );
288 }
289 }
290}