Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

to not log error on unsupported metric type #777

Merged
merged 4 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions dogstatsd/src/dogstatsd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ use std::net::SocketAddr;
use std::str::Split;
use std::sync::{Arc, Mutex};

use tracing::{debug, error};

use crate::aggregator::Aggregator;
use crate::errors::ParseError::UnsupportedType;
use crate::metric::{parse, Metric};
use tracing::{debug, error};

pub struct DogStatsD {
cancel_token: tokio_util::sync::CancellationToken,
Expand Down Expand Up @@ -92,7 +92,12 @@ impl DogStatsD {
.filter_map(|m| match parse(m.as_str()) {
Ok(metric) => Some(metric),
Err(e) => {
error!("Failed to parse metric {}: {}", m, e);
// unsupported type is quite common with dd_trace metrics. Avoid perf issue and
// log spam in that case
match e {
UnsupportedType(_) => debug!("Unsupported metric type: {}. {}", m, e),
_ => error!("Failed to parse metric {}: {}", m, e),
}
None
}
})
Expand Down
6 changes: 4 additions & 2 deletions dogstatsd/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
//! Error types for `metrics` module

/// Errors for the function [`crate::metric::Metric::parse`]
#[derive(Debug, thiserror::Error, Clone, Copy, Eq, PartialEq)]
#[derive(Debug, thiserror::Error, PartialEq)]
pub enum ParseError {
/// Parse failure given in text
#[error("parse failure: {0}")]
Raw(&'static str),
Raw(String),
#[error("unsupported metric type: {0}")]
UnsupportedType(String),
}

/// Failure to create a new `Aggregator`
Expand Down
48 changes: 31 additions & 17 deletions dogstatsd/src/metric.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ use ustr::Ustr;
pub const EMPTY_TAGS: SortedTags = SortedTags { values: Vec::new() };

lazy_static! {
static ref METRIC_REGEX: regex::Regex = Regex::new(
r"^(?P<name>[^:]+):(?P<values>[^|]+)\|(?P<type>[cgd])(?:\|@(?P<sample_rate>[\d.]+))?(?:\|#(?P<tags>[^|]+))?(?:\|c:(?P<container_id>[^|]+))?$",
static ref METRIC_REGEX: Regex = Regex::new(
r"^(?P<name>[^:]+):(?P<values>[^|]+)\|(?P<type>[a-zA-Z]+)(?:\|@(?P<sample_rate>[\d.]+))?(?:\|#(?P<tags>[^|]+))?(?:\|c:(?P<container_id>[^|]+))?$",
bantonsson marked this conversation as resolved.
Show resolved Hide resolved
).expect("Failed to create metric regex");
}

Expand Down Expand Up @@ -70,10 +70,10 @@ impl SortedTags {
// Validate that the tags have the right form.
for (i, part) in tag_parts.filter(|s| !s.is_empty()).enumerate() {
if i >= constants::MAX_TAGS {
return Err(ParseError::Raw("Too many tags"));
return Err(ParseError::Raw(format!("Too many tags, more than {i}")));
}
if !part.contains(':') {
return Err(ParseError::Raw("Invalid tag format"));
return Err(ParseError::Raw("Invalid tag format".to_string()));
}
if let Some((k, v)) = part.split_once(':') {
parsed_tags.push((Ustr::from(k), Ustr::from(v)));
Expand Down Expand Up @@ -185,16 +185,20 @@ pub fn parse(input: &str) -> Result<Metric, ParseError> {
tags = None;
}
let val = first_value(caps.name("values").unwrap().as_str())?;
let metric_value = match caps.name("type").unwrap().as_str() {
let t = caps.name("type").unwrap().as_str();
let metric_value = match t {
"c" => MetricValue::Count(val),
"g" => MetricValue::Gauge(val),
"d" => {
let sketch = &mut DDSketch::default();
sketch.insert(val);
MetricValue::Distribution(sketch.to_owned())
}
"h" | "s" | "ms" => {
return Err(ParseError::UnsupportedType(t.to_string()));
}
_ => {
return Err(ParseError::Raw("Unsupported metric type"));
return Err(ParseError::Raw(format!("Invalid metric type: {t}")));
}
};
let name = Ustr::from(caps.name("name").unwrap().as_str());
Expand All @@ -206,16 +210,16 @@ pub fn parse(input: &str) -> Result<Metric, ParseError> {
id,
});
}
Err(ParseError::Raw("Invalid metric format"))
Err(ParseError::Raw(format!("Invalid metric format {input}")))
}

fn first_value(values: &str) -> Result<f64, ParseError> {
match values.split(':').next() {
Some(v) => match v.parse::<f64>() {
Ok(v) => Ok(v),
Err(_) => Err(ParseError::Raw("Invalid value")),
Err(e) => Err(ParseError::Raw(format!("Invalid value {e}"))),
},
None => Err(ParseError::Raw("Missing value")),
None => Err(ParseError::Raw("Missing value".to_string())),
}
}

Expand Down Expand Up @@ -365,7 +369,7 @@ mod tests {
};
let result = parse(&input);

assert_eq!(result.unwrap_err(),ParseError::Raw("Invalid metric format"));
assert_eq!(result.unwrap_err(),ParseError::Raw(format!("Invalid metric format {input}")));
}

#[test]
Expand All @@ -386,18 +390,17 @@ mod tests {
};
let result = parse(&input);

assert_eq!(
result.unwrap_err(),
ParseError::Raw("Invalid metric format")
);
let verify = result.unwrap_err().to_string();
println!("{}", verify);
assert!(verify.starts_with("parse failure: Invalid metric format "));
}

#[test]
#[cfg_attr(miri, ignore)]
fn parse_unsupported_metric_type(
name in metric_name(),
values in metric_values(),
mtype in "[abefhijklmnopqrstuvwxyz]",
mtype in "[abefijklmnopqrtuvwxyz]",
tagset in metric_tagset()
) {
let input = if let Some(ref tagset) = tagset {
Expand All @@ -409,7 +412,7 @@ mod tests {

assert_eq!(
result.unwrap_err(),
ParseError::Raw("Invalid metric format")
ParseError::Raw(format!("Invalid metric type: {mtype}"))
);
}

Expand Down Expand Up @@ -469,7 +472,7 @@ mod tests {
fn parse_too_many_tags() {
// 33
assert_eq!(parse("foo:1|g|#a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2,c:3").unwrap_err(),
ParseError::Raw("Too many tags"));
ParseError::Raw("Too many tags, more than 32".to_string()));

// 32
assert!(parse("foo:1|g|#a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2,c:3,a:1,b:2").is_ok());
Expand All @@ -492,4 +495,15 @@ mod tests {
fn parse_container_id() {
assert!(parse("containerid.metric:0|c|#env:dev,client_transport:udp|c:0000000000000000000000000000000000000000000000000000000000000000").is_ok());
}

#[test]
fn parse_tracer_metric() {
let input = "datadog.tracer.flush_duration:0.785551|ms|#lang:go,lang_version:go1.23.2,env:redacted_env,_dd.origin:lambda,runtime-id:redacted_runtime,tracer_version:v1.70.1,service:redacted_service,env:redacted_env,service:redacted_service,version:redacted_version";
let expected_error = "ms".to_string();
if let ParseError::UnsupportedType(actual_error) = parse(input).unwrap_err() {
assert_eq!(actual_error, expected_error);
} else {
panic!("Expected UnsupportedType error");
}
}
}
Loading