Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@

/**
* GraphiteNamePattern is initialised with a simplified glob pattern that only allows '*' as special
* character. Examples of valid patterns:
* character. Accepts a broad range of metric name patterns including single-level names, names with
* underscores, hyphens, and colons. Examples of valid patterns:
*
* <ul>
* <li>org.test.controller.gather.status.400
* <li>org.test.controller.gather.status.*
* <li>org.test.controller.*.status.*
* <li>*.test.controller.*.status.*
* <li>app_metric_some_count
* <li>io.dropwizard.jetty.MutableServletContextHandler.*-requests
* </ul>
*
* <p>It contains logic to match a metric name and to extract named parameters from it.
Expand All @@ -32,6 +35,10 @@ class GraphiteNamePattern {
* @param pattern The glob style pattern to be used.
*/
GraphiteNamePattern(String pattern) throws IllegalArgumentException {
if (pattern.contains("**")) {
throw new IllegalArgumentException(
String.format("Provided pattern [%s] must not contain '**' (double-star glob)", pattern));
}
if (!VALIDATION_PATTERN.matcher(pattern).matches()) {
throw new IllegalArgumentException(
String.format("Provided pattern [%s] does not matches [%s]", pattern, METRIC_GLOB_REGEX));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@
* and new labels based on this config.
*/
public final class MapperConfig {
// each part of the metric name between dots
private static final String METRIC_PART_REGEX = "[a-zA-Z_0-9](-?[a-zA-Z0-9_])+";
// Simplified GLOB: we can have "*." at the beginning and "*" only at the end
// Each part of the metric name between dots. Accepts letters, digits, underscores, hyphens,
// colons, and glob wildcards (*) to support a broad range of metric naming conventions.
private static final String METRIC_PART_REGEX = "[a-zA-Z_0-9*][a-zA-Z0-9_:\\-*]*";
// Simplified GLOB: accepts single-level names, dot-separated names, and glob patterns with '*'.
// The pattern requires at least one non-empty segment and does not allow empty segments (double
// dots) or empty/whitespace-only strings. The '**' glob is rejected separately in validateMatch.
static final String METRIC_GLOB_REGEX =
"^(\\*\\.|" + METRIC_PART_REGEX + "\\.)+(\\*|" + METRIC_PART_REGEX + ")$";
"^(" + METRIC_PART_REGEX + ")(\\." + METRIC_PART_REGEX + ")*$";
// Labels validation.
private static final String LABEL_REGEX = "^[a-zA-Z_][a-zA-Z0-9_]+$";
private static final Pattern MATCH_EXPRESSION_PATTERN = Pattern.compile(METRIC_GLOB_REGEX);
Expand Down Expand Up @@ -109,6 +112,10 @@ public void setLabels(Map<String, String> labels) {
}

private void validateMatch(String match) {
if (match.contains("**")) {
throw new IllegalArgumentException(
String.format("Match expression [%s] must not contain '**' (double-star glob)", match));
}
if (!MATCH_EXPRESSION_PATTERN.matcher(match).matches()) {
throw new IllegalArgumentException(
String.format(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,12 @@ void createNew_WHEN_InvalidPattern_THEN_ShouldThrowException() {
List<String> invalidPatterns =
Arrays.asList(
"",
"a",
"1org",
"1org.",
"org.",
"org.**",
"org.**",
"org.company-",
"org.company-.",
"org.company-*",
"org.company.**",
"org.company.**-",
"org.com*pany.*",
"org.test.contr.oller.gather.status..400",
"org.test.controller.gather.status..400");
for (String pattern : invalidPatterns) {
Expand All @@ -47,7 +41,22 @@ void createNew_WHEN_ValidPattern_THEN_ShouldCreateThePatternSuccessfully() {
"org.test.controller.*.status.*",
"*.test.controller.*.status.*",
"*.test.controller-1.*.status.*",
"*.amazing-test.controller-1.*.status.*");
"*.amazing-test.controller-1.*.status.*",
// Single-level names (previously rejected, fixes #461)
"a",
"1org",
"app_metric_some_count",
// Names with colons (fixes #645)
"my:metric:name",
"org.company:metric.*",
// Names with hyphens at boundaries
"org.company-",
"org.company-*",
// Embedded glob in segment (fixes #518)
"org.com*pany.*",
"io.dropwizard.jetty.MutableServletContextHandler.*-requests",
// Standalone glob
"*");
for (String pattern : validPatterns) {
new GraphiteNamePattern(pattern);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,52 @@ void setLabels_WHEN_ExpressionDoesnNotMatchPattern_ThrowException() {
.isThrownBy(() -> mapperConfig.setLabels(labels));
}

@Test
void setMatch_WHEN_SingleLevelName_AllGood() {
final MapperConfig mapperConfig = new MapperConfig();
mapperConfig.setMatch("app_metric_some_count");
assertThat(mapperConfig.getMatch()).isEqualTo("app_metric_some_count");
}

@Test
void setMatch_WHEN_NameWithColons_AllGood() {
final MapperConfig mapperConfig = new MapperConfig();
mapperConfig.setMatch("my:metric:name");
assertThat(mapperConfig.getMatch()).isEqualTo("my:metric:name");
}

@Test
void setMatch_WHEN_EmbeddedGlobInSegment_AllGood() {
final MapperConfig mapperConfig = new MapperConfig();
mapperConfig.setMatch("io.dropwizard.jetty.MutableServletContextHandler.*-requests");
assertThat(mapperConfig.getMatch())
.isEqualTo("io.dropwizard.jetty.MutableServletContextHandler.*-requests");
}

@Test
void setMatch_WHEN_DoubleStarGlob_ThrowException() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new MapperConfig().setMatch("org.**"));
}

@Test
void setMatch_WHEN_EmptyString_ThrowException() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new MapperConfig().setMatch(""));
}

@Test
void setMatch_WHEN_TrailingDot_ThrowException() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new MapperConfig().setMatch("org.company."));
}

@Test
void setMatch_WHEN_DoubleDot_ThrowException() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new MapperConfig().setMatch("org..company"));
}

@Test
void toString_WHEN_EmptyConfig_AllGood() {
final MapperConfig mapperConfig = new MapperConfig();
Expand Down