Skip to content

Commit

Permalink
[feature] adding metric util and metric filter
Browse files Browse the repository at this point in the history
PRD-221992
  • Loading branch information
shahryarSafizadeh authored Feb 18, 2024
1 parent af8b52a commit 7cfcf6b
Show file tree
Hide file tree
Showing 9 changed files with 339 additions and 1 deletion.
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,68 @@ serviceLog.enabled =false
```

attention: if you have any input of type HttpServletRequest, this input will be ignored in logging.
### Metrics
one of our goals is the ability of monitoring the application performance by defining proper metrics and registering them
via prometheus registry. to accomplish this, we provided a way for registering your custom metrics and update them whenever you want.
additionally, you can exclude your unwanted metrics to have cleaner and more customized prometheus report.
#### registering metrics
for registering metrics, first we define a default bean as below:
```
@Bean
@ConditionalOnMissingBean
public MeterUtil meterUtil() {
return new MeterUtil();
}
```
we have 3 types of metric : TIMER , COUNTER , GAUGE. you can register your custom metrics with one of these types as shown below :
```
meterUtil.registerMeter(MeterType.Timer,
"meterName",
"description",
tags);
```
as you see, you should specify your metric with a valid type and a name. additionally, if wanted, you can add description or tags to your registering metric.
#### updating metrics
we have three types of metric that for each one, we provided a proper updating mechanism:
1. updating COUNTER meter: by updating this type of meter you will increment the value of it.<br>
example:
```
meterUtil.updateCounterMeter("meterName", tags);
```
- note : you can pass null instead of _tags_ if you did not specify tags for your metric in the first place.

2.updating TIMER meter: by updating the timer meter, you can record your wanted duration in milliseconds.<br>
example:
```
meterUtil.updateTimerMeter("meterName", tags, 1000);
```
- note : you can pass null instead of _tags_ if you did not specify tags for your metric in the first place.

3.updating GAUGE meter: by updating this type, you can both increment and decrement the value of it.<br>
example:
```
meterUtil.updateGaugeMeterByIncrementing("meterName", tags);
meterUtil.updateGaugeMeterByDecrementing("meterName", tags);
```
- note : you can pass null instead of _tags_ if you did not specify tags for your metric in the first place.

#### excluding metrics
in order of excluding wanted metrics from prometheus metrics, we define two ways of exclusion:
1. excluding metrics by metric name <br>
- with the config below, you can simply tell us which metric names you want to exclude:
```
metric.filter.excluded-meter-names=meterName1,meterName2,meterName3
```
2. excluding metrics by metric tags
- with the config below, you can specify the tags you want the metrics having them to be excluded:
```
metric.filter.excluded-meter-tags.key1=value1
metric.filter.excluded-meter-tags.key2=value2
```
- note that basically a tag includes a key and a value in it, and the above example represents that
as a user, we want to exclude metrics with tag1(key1,value1) and tag2(key2,value2).

### Sample Project
You can find a sample project in tosan-httpserver-spring-boot-sample module
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
server.port = 8099

serviceLog.enabled=true
http.log.format=json
http.log.format=json

metric.filter.enable=true
4 changes: 4 additions & 0 deletions tosan-httpserver-spring-boot-starter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
<artifactId>tosan-httpserver-spring-boot-starter</artifactId>

<dependencies>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import com.tosan.http.server.starter.filter.HttpMdcFilter;
import com.tosan.http.server.starter.filter.HttpStatisticsFilter;
import com.tosan.http.server.starter.logger.JsonServiceLogger;
import com.tosan.http.server.starter.metrics.MeterFilterConfig;
import com.tosan.http.server.starter.metrics.MetricFilter;
import com.tosan.http.server.starter.metrics.util.MeterUtil;
import com.tosan.http.server.starter.util.*;
import com.tosan.tools.mask.starter.config.SecureParameter;
import com.tosan.tools.mask.starter.config.SecureParametersConfig;
Expand Down Expand Up @@ -157,4 +160,23 @@ public ToStringJsonUtil toStringJsonUtil(@Qualifier("http-server-util-regex-repl
JsonReplaceHelperDecider jsonReplaceHelperDecider) {
return new ToStringJsonUtil(jsonReplaceHelperDecider);
}

@Bean
@ConditionalOnMissingBean
public MeterUtil meterUtil() {
return new MeterUtil();
}

@Bean
@ConditionalOnMissingBean
public MeterFilterConfig meterFilterConfig() {
return new MeterFilterConfig();
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(value = "metric.filter.enable", havingValue = "true")
public MetricFilter metricFilter(MeterFilterConfig meterFilterConfig) {
return new MetricFilter(meterFilterConfig);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.tosan.http.server.starter.metrics;

import java.util.concurrent.atomic.AtomicInteger;

/**
* @author Shahryar Safizadeh
* @since 2/13/2024
*/
public class GaugeValue {
private static final AtomicInteger value = new AtomicInteger(0);

public void increment() {
value.incrementAndGet();
}

public void decrement() {
value.decrementAndGet();
}

public AtomicInteger getValue() {
return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.tosan.http.server.starter.metrics;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

import java.util.Map;

/**
* @author Shahryar Safizadeh
* @since 2/17/2024
*/
@ConfigurationProperties(prefix = "metric.filter")
@Validated
public class MeterFilterConfig {

private String[] excludedMeterNames;
private Map<String, String> excludedMeterTags;

public String[] getExcludedMeterNames() {
return excludedMeterNames;
}

public void setExcludedMeterNames(String[] excludedMeterNames) {
this.excludedMeterNames = excludedMeterNames;
}

public Map<String, String> getExcludedMeterTags() {
return excludedMeterTags;
}

public void setExcludedMeterTags(Map<String, String> excludedMeterTags) {
this.excludedMeterTags = excludedMeterTags;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.tosan.http.server.starter.metrics;

import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.config.MeterFilterReply;

import java.util.Arrays;

public class MetricFilter implements MeterFilter {

private MeterFilterConfig meterFilterConfig;

public MetricFilter(MeterFilterConfig meterFilterConfig) {
this.meterFilterConfig = meterFilterConfig;
}

@Override
public MeterFilterReply accept(Meter.Id id) {
if (meterFilterConfig.getExcludedMeterNames() != null && Arrays.asList(meterFilterConfig.getExcludedMeterNames()).contains(id.getName())) {
return MeterFilterReply.DENY;
}
if (meterFilterConfig.getExcludedMeterTags() != null && !meterFilterConfig.getExcludedMeterTags().isEmpty()) {
for (String key : meterFilterConfig.getExcludedMeterTags().keySet()) {
if (id.getTags().contains(Tag.of(key, meterFilterConfig.getExcludedMeterTags().get(key)))) {
return MeterFilterReply.DENY;
}
}
}
return MeterFilterReply.NEUTRAL;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.tosan.http.server.starter.metrics.enumuration;

/**
* @author Shahryar Safizadeh
* @since 2/13/2024
*/
public enum MeterType {
TIMER,
COUNTER,
GAUGE;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package com.tosan.http.server.starter.metrics.util;

import com.tosan.http.server.starter.metrics.GaugeValue;
import com.tosan.http.server.starter.metrics.enumuration.MeterType;
import io.micrometer.core.instrument.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
* @author Shahryar Safizadeh
* @since 2/13/2024
*/
public class MeterUtil {

private static final Logger LOGGER = LoggerFactory.getLogger(MeterUtil.class);
private MeterRegistry meterRegistry;
private final Map<String, Meter> meters = new HashMap<>();
private final Map<String, GaugeValue> gaugeMeters = new HashMap<>();

@Autowired
public void setMeterRegistry(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}

public Meter registerMeter(MeterType meterType, String meterName, String description, Tags tags) {
String key = createKey(meterName, tags);
if (!meters.containsKey(key)) {
synchronized (meters) {
meters.computeIfAbsent(key, ignore -> getMeter(meterType, meterName, description, tags));
}
}
return meters.get(key);
}

public void updateTimerMeter(String metricName, Tags tags, long duration) {
String key = createKey(metricName, tags);
if (!meters.isEmpty() && meters.get(key) != null) {
Meter meter = meters.get(key);
((Timer) meter).record(duration, TimeUnit.MILLISECONDS);
}
}

public void updateCounterMeter(String metricName, Tags tags) {
String key = createKey(metricName, tags);
if (!meters.isEmpty() && meters.get(key) != null) {
Meter meter = meters.get(key);
((Counter) meter).increment();
}
}

public void updateGaugeMeterByIncrementing(String metricName, Tags tags) {
String key = createKey(metricName, tags);
if (!meters.isEmpty() && gaugeMeters.get(key) != null) {
GaugeValue gaugeValue = gaugeMeters.get(createKey(metricName, tags));
gaugeValue.increment();
gaugeMeters.put(createKey(metricName, tags), gaugeValue);
}
}

public void updateGaugeMeterByDecrementing(String metricName, Tags tags) {
String key = createKey(metricName, tags);
if (!gaugeMeters.isEmpty() && gaugeMeters.get(key) != null) {
GaugeValue gaugeValue = gaugeMeters.get(createKey(metricName, tags));
gaugeValue.decrement();
gaugeMeters.put(createKey(metricName, tags), gaugeValue);
}
}

private Meter getMeter(MeterType meterType, String meterName, String description, Tags tags) {
if (meterType != null) {
switch (meterType) {
case COUNTER: {
return registerCounterMeter(meterName, description, tags);
}
case TIMER: {
return registerTimerMeter(meterName, description, tags);
}
case GAUGE: {
return registerGaugeMeter(meterName, description, tags);
}
}
}
LOGGER.error("No meterType selected.");
return null;
}

private String createKey(String meterName, Tags tags) {
if (meterName == null) {
throw new RuntimeException("meter name is null");
}
if (tags != null) {
return String.join("_", meterName, tags.stream().map(Tag::getValue).collect(Collectors.joining("_")));
} else {
return meterName;
}
}

private Counter registerCounterMeter(String metricName, String description, Tags tags) {
if (tags == null) {
return Counter.builder(metricName)
.description(description)
.register(meterRegistry);
} else {
return Counter.builder(metricName)
.description(description)
.tags(tags)
.register(meterRegistry);
}
}

private Timer registerTimerMeter(String metricName, String description, Tags tags) {
if (tags == null) {
return Timer.builder(metricName)
.description(description)
.publishPercentiles(0.5, 0.95)
.register(meterRegistry);
} else {
return Timer.builder(metricName)
.description(description)
.tags(tags)
.publishPercentiles(0.5, 0.95)
.register(meterRegistry);
}
}

private Gauge registerGaugeMeter(String metricName, String desctiption, Tags tags) {
GaugeValue gaugeValue = new GaugeValue();
Gauge gauge;
if (tags == null) {
gauge = Gauge.builder(metricName, gaugeValue::getValue)
.description(desctiption)
.register(meterRegistry);
} else {
gauge = Gauge.builder(metricName, gaugeValue::getValue)
.description(desctiption)
.tags(tags)
.register(meterRegistry);
}
gaugeMeters.put(createKey(metricName, tags), gaugeValue);
return gauge;
}
}

0 comments on commit 7cfcf6b

Please sign in to comment.