use std::fmt::Display;
use crate::common_metric_data::CommonMetricDataInternal;
use crate::error::{Error, ErrorKind};
use crate::metrics::labeled::{combine_base_identifier_and_label, strip_label};
use crate::metrics::CounterMetric;
use crate::CommonMetricData;
use crate::Glean;
use crate::Lifetime;
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ErrorType {
InvalidValue,
InvalidLabel,
InvalidState,
InvalidOverflow,
}
impl ErrorType {
pub fn as_str(&self) -> &'static str {
match self {
ErrorType::InvalidValue => "invalid_value",
ErrorType::InvalidLabel => "invalid_label",
ErrorType::InvalidState => "invalid_state",
ErrorType::InvalidOverflow => "invalid_overflow",
}
}
pub fn iter() -> impl Iterator<Item = Self> {
[
ErrorType::InvalidValue,
ErrorType::InvalidLabel,
ErrorType::InvalidState,
ErrorType::InvalidOverflow,
]
.iter()
.copied()
}
}
impl TryFrom<i32> for ErrorType {
type Error = Error;
fn try_from(value: i32) -> Result<ErrorType, Self::Error> {
match value {
0 => Ok(ErrorType::InvalidValue),
1 => Ok(ErrorType::InvalidLabel),
2 => Ok(ErrorType::InvalidState),
3 => Ok(ErrorType::InvalidOverflow),
e => Err(ErrorKind::Lifetime(e).into()),
}
}
}
fn get_error_metric_for_metric(meta: &CommonMetricDataInternal, error: ErrorType) -> CounterMetric {
let identifier = meta.base_identifier();
let name = strip_label(&identifier);
let mut send_in_pings = meta.inner.send_in_pings.clone();
let ping_name = "metrics".to_string();
if !send_in_pings.contains(&ping_name) {
send_in_pings.push(ping_name);
}
CounterMetric::new(CommonMetricData {
name: combine_base_identifier_and_label(error.as_str(), name),
category: "glean.error".into(),
lifetime: Lifetime::Ping,
send_in_pings,
..Default::default()
})
}
pub fn record_error<O: Into<Option<i32>>>(
glean: &Glean,
meta: &CommonMetricDataInternal,
error: ErrorType,
message: impl Display,
num_errors: O,
) {
let metric = get_error_metric_for_metric(meta, error);
log::warn!("{}: {}", meta.base_identifier(), message);
let to_report = num_errors.into().unwrap_or(1);
debug_assert!(to_report > 0);
metric.add_sync(glean, to_report);
}
pub fn test_get_num_recorded_errors(
glean: &Glean,
meta: &CommonMetricDataInternal,
error: ErrorType,
) -> Result<i32, String> {
let metric = get_error_metric_for_metric(meta, error);
metric.get_value(glean, Some("metrics")).ok_or_else(|| {
format!(
"No error recorded for {} in 'metrics' store",
meta.base_identifier(),
)
})
}
#[cfg(test)]
mod test {
use super::*;
use crate::metrics::*;
use crate::tests::new_glean;
#[test]
fn error_type_i32_mapping() {
let error: ErrorType = std::convert::TryFrom::try_from(0).unwrap();
assert_eq!(error, ErrorType::InvalidValue);
let error: ErrorType = std::convert::TryFrom::try_from(1).unwrap();
assert_eq!(error, ErrorType::InvalidLabel);
let error: ErrorType = std::convert::TryFrom::try_from(2).unwrap();
assert_eq!(error, ErrorType::InvalidState);
let error: ErrorType = std::convert::TryFrom::try_from(3).unwrap();
assert_eq!(error, ErrorType::InvalidOverflow);
}
#[test]
fn recording_of_all_error_types() {
let (glean, _t) = new_glean(None);
let string_metric = StringMetric::new(CommonMetricData {
name: "string_metric".into(),
category: "telemetry".into(),
send_in_pings: vec!["store1".into(), "store2".into()],
disabled: false,
lifetime: Lifetime::User,
..Default::default()
});
let expected_invalid_values_errors: i32 = 1;
let expected_invalid_labels_errors: i32 = 2;
record_error(
&glean,
string_metric.meta(),
ErrorType::InvalidValue,
"Invalid value",
None,
);
record_error(
&glean,
string_metric.meta(),
ErrorType::InvalidLabel,
"Invalid label",
expected_invalid_labels_errors,
);
let invalid_val =
get_error_metric_for_metric(string_metric.meta(), ErrorType::InvalidValue);
let invalid_label =
get_error_metric_for_metric(string_metric.meta(), ErrorType::InvalidLabel);
for &store in &["store1", "store2", "metrics"] {
assert_eq!(
Some(expected_invalid_values_errors),
invalid_val.get_value(&glean, Some(store))
);
assert_eq!(
Some(expected_invalid_labels_errors),
invalid_label.get_value(&glean, Some(store))
);
}
}
}