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
1 change: 1 addition & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@
<excludes />
<allows>
<allow>io[.]micrometer[.]core[.]instrument[.]MeterRegistry</allow>
<allow>io[.]micrometer[.]core[.]instrument[.]config[.]MeterFilter</allow>
<allow>io[.]opentelemetry[.]api[.]OpenTelemetry</allow>
<allow>org[.]apache[.]hadoop[.]io[.]Text</allow>
<allow>org[.]apache[.]accumulo[.]core[.]client[.].*</allow>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,19 @@
*/
package org.apache.accumulo.core.spi.metrics;

import static java.util.Objects.requireNonNull;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.logging.LoggingMeterRegistry;
import io.micrometer.core.instrument.logging.LoggingRegistryConfig;

Expand All @@ -49,6 +54,21 @@
* <pre>
* general.custom.metrics.opts.logging.step = 10s
* </pre>
*
* To filter out meters that start with any particular prefix, set
* {@code general.custom.metrics.opts.meter.id.filters} in the Accumulo configuration.
*
* <pre>
* general.custom.metrics.opts.meter.id.filters = prefix1,prefix2,prefix3
*
* </pre>
*
* To assign a custom delimiter to filters, set
* {@code general.custom.metrics.opts.meter.id.delimiter} in the Accumulo configuration.
Comment on lines +61 to +67
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of making the delimiter configurable, a different way of doing this, one that is more consistent with how we have set other properties, would be to support something like:

general.custom.metrics.loggingfactory.filter.exclude.1=prefix1
general.custom.metrics.loggingfactory.filter.exclude.2=prefix2
general.custom.metrics.loggingfactory.filter..exclude.3=prefix3

That way you don't have to worry about a delimiter at all.

Alternatively, make it a single regex filter:

general.custom.metrics.loggingfactory.filter.deny.regex=^(prefix1|prefix2|prefix3).*$

*
* <pre>
* general.custom.metrics.opts.meter.id.delimiter = delimiter
* </pre>
*/
public class LoggingMeterRegistryFactory implements MeterRegistryFactory {

Expand Down Expand Up @@ -76,6 +96,49 @@ public MeterRegistry create(final InitParameters params) {

LOG.info("Creating logging metrics registry with params: {}", params);
metricsProps.putAll(params.getOptions());
return LoggingMeterRegistry.builder(lconf).loggingSink(metricConsumer).build();

MeterRegistry registry =
LoggingMeterRegistry.builder(lconf).loggingSink(metricConsumer).build();
String filters = metricsProps.get("meter.id.filters");
// Sets the default delimiter if none is specified
String delimiter = metricsProps.getOrDefault("meter.id.delimiter", ",");

if (filters != null && delimiter != null) {
registry.config().meterFilter(getMeterFilter(filters, delimiter));
}
return registry;
}

/**
* This function uses terms specified in the patternList parameter to filter out specific metrics
* that the user doesn't want.
*
* @param patternList a delimited set of terms that will filter out meters that start with any one
* of those terms.
* @param delimiter that contains either a user specified delimiter, or the default comma
* delimiter to split the patternList.
* @return a predicate with the type of MeterFilter, that describes which metrics to deny and
* subsequently filter out.
*/
public static MeterFilter getMeterFilter(String patternList, String delimiter) {
requireNonNull(patternList, "patternList must not be null");

// Trims whitespace and all other non-visible characters.
patternList = patternList.replaceAll("\\s+", "");

// Gets the default delimiter or the delimiter the user supplied
String[] patterns = patternList.split(delimiter);
Predicate<Meter.Id> finalPredicate = id -> false;

if (patternList.isEmpty()) {
return MeterFilter.deny(finalPredicate);
}

for (String prefix : patterns) {
Predicate<Meter.Id> predicate = id -> id.getName().startsWith(prefix);
finalPredicate = finalPredicate.or(predicate);

}
return MeterFilter.deny(finalPredicate);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,150 @@
*/
package org.apache.accumulo.core.spi.metrics;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;

import java.util.List;
import java.util.Map;

import org.apache.accumulo.core.spi.common.ServiceEnvironment;
import org.junit.jupiter.api.Test;

import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.logging.LoggingMeterRegistry;

class LoggingMeterRegistryFactoryTest {

@Test
public void createTest() {
LoggingMeterRegistryFactory factory = new LoggingMeterRegistryFactory();
var reg = factory.create(new LoggingMetricsParams());
var reg =
factory.create(new LoggingMetricsParams(Map.of("prop1", "abc", "logging.step", "1s")));
assertInstanceOf(LoggingMeterRegistry.class, reg);
}

private static class LoggingMetricsParams implements MeterRegistryFactory.InitParameters {

private final Map<String,String> options;

public LoggingMetricsParams(Map<String,String> options) {
this.options = options;
}

@Override
public Map<String,String> getOptions() {
return options;
// note: general.custom.metrics.opts. is expected to be stripped before passing the options.
return Map.of("prop1", "abc", "logging.step", "1s");
// return Map.of("prop1", "abc", "logging.step", "1s", "meter.id.filters", "Foo,Bar,Hat");
}

@Override
public ServiceEnvironment getServiceEnv() {
return null;
}
}

/**
* Create a factory and test that the given filters are applied to all ID's that match the given
* meter.id.filters.
*/
@Test
public void createWithFilters() {
LoggingMeterRegistryFactory factory = new LoggingMeterRegistryFactory();
var reg = factory.create(new LoggingMetricsParams(
Map.of("prop1", "abc", "logging.step", "1s", "meter.id.filters", "Foo,Bar,Hat")));
reg.timer("FooTimer", "tag1", "tag2"); // Yes filters this out
reg.timer("ProperTimer", "tag1", "tag2");

List<Meter> meterReg = reg.getMeters(); // Does this only contain ProperTimer? Yes
assertEquals("ProperTimer", meterReg.get(0).getId().getName());
}

/**
* Verify that the filters can be split by either the default or user-specified delimiter.
*/
@Test
public void testNonCommaDelimiter() {
LoggingMeterRegistryFactory factory = new LoggingMeterRegistryFactory();
var reg = factory.create(new LoggingMetricsParams(Map.of("prop1", "abc", "logging.step", "1s",
"meter.id.filters", "Foo:Bar:Hat", "meter.id.delimiter", ":")));
reg.timer("FooTimer", "tag1", "tag2");
reg.timer("ProperTimer", "tag1", "tag2");
reg.timer("BarTimer", "tag1", "tag2");
reg.timer("HatTimer", "tag1", "tag2");
reg.gauge("BarGauge", 10);
reg.gauge("ProperGauge", 10);
reg.counter("HatCounter", "tag1", "tag2");
reg.counter("ProperCounter", "tag1", "tag2");

List<Meter> meterReg = reg.getMeters();
StringBuilder allMeters = new StringBuilder();
for (Meter m : meterReg) {
allMeters.append(m.getId().getName());
}

assertFalse(allMeters.toString().contains("Foo"));
assertFalse(allMeters.toString().contains("Bar"));
assertFalse(allMeters.toString().contains("Hat"));
}

/**
* Verify that {@link LoggingMeterRegistryFactory#getMeterFilter(String, String)} only filters out
* meters where the filter terms are prefixes for the ID.
*/
@Test
public void testIfPatternIsNotFirst() {
LoggingMeterRegistryFactory factory = new LoggingMeterRegistryFactory();
var reg = factory.create(new LoggingMetricsParams(
Map.of("prop1", "abc", "logging.step", "1s", "meter.id.filters", "Foo,Bar,Hat")));
reg.timer("FooTimer", "tag1", "tag2");
reg.timer("TimerFoo", "tag1", "tag2");

List<Meter> meterReg = reg.getMeters();
StringBuilder allMeters = new StringBuilder();
for (Meter m : meterReg) {
allMeters.append(m.getId().getName());
}
assertEquals("TimerFoo", allMeters.toString());
}

/**
* Test that {@link LoggingMeterRegistryFactory#getMeterFilter(String, String)} can filter out
* meters correctly even when given duplicates.
*/
@Test
public void testIfDuplicatePatterns() {
LoggingMeterRegistryFactory factory = new LoggingMeterRegistryFactory();
var reg = factory.create(new LoggingMetricsParams(
Map.of("prop1", "abc", "logging.step", "1s", "meter.id.filters", "Foo,Foo,Foo")));
reg.timer("FooTimer", "tag1", "tag2");

List<Meter> meterReg = reg.getMeters();
StringBuilder allMeters = new StringBuilder();
for (Meter m : meterReg) {
allMeters.append(m.getId().getName());
}

assertEquals("", allMeters.toString());
}

/**
* Test that when {@link LoggingMeterRegistryFactory#getMeterFilter(String, String)} is given an
* empty string of filters, all IDs are still passed through.
*/
@Test
public void testEmptyPatternList() {
LoggingMeterRegistryFactory factory = new LoggingMeterRegistryFactory();
var reg = factory.create(new LoggingMetricsParams(
Map.of("prop1", "abc", "logging.step", "1s", "meter.id.filters", "")));
reg.timer("FooTimer", "tag1", "tag2");
List<Meter> meterReg = reg.getMeters();
StringBuilder allMeters = new StringBuilder();
for (Meter m : meterReg) {
allMeters.append(m.getId().getName());
}
assertEquals("FooTimer", allMeters.toString());
}

}