Skip to content
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ openapi.validation.excluded-headers[0]=User-Agent: .*(bingbot|googlebot).*
openapi.validation.validation-report-throttle-wait-seconds=10

# Throttle the validation reporting (logs & metrics) to a maximum of 1 log/metric per 10 seconds.
# Default is "openapi.validation.error".
openapi.validation.validation-report-metric-name=openapi.violation
# Default is "openapi.validation".
openapi.validation.validation-report-metric-name=validation.openapi

# Add additional tags to be logged with metrics. They should be in the format {KEY}={VALUE},{KEY}={VALUE}
# Default is no additional tags.
Expand Down Expand Up @@ -199,7 +199,7 @@ result in a violation error with log level `warn`.

```java
@Configuration
public class ValidatorConfiguration {
public class OpenApiValidatorConfiguration {
@Bean
public ValidatorConfiguration buildValidatorConfiguration() {
return new ValidatorConfigurationBuilder()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package com.getyourguide.openapi.validation.example.configuration;

import com.getyourguide.openapi.validation.api.log.LoggerExtension;
import com.getyourguide.openapi.validation.api.metrics.MetricsReporter;
import com.getyourguide.openapi.validation.api.metrics.client.MetricsClient;
import com.getyourguide.openapi.validation.example.logging.ExampleLoggerExtension;
import com.getyourguide.openapi.validation.metrics.LoggingMetricsReporter;
import com.getyourguide.openapi.validation.metrics.client.LoggingMetricsClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ExampleConfiguration {
@Bean
public MetricsReporter metricsReporter() {
return new LoggingMetricsReporter();
public MetricsClient metricsClient() {
return new LoggingMetricsClient();
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package com.getyourguide.openapi.validation.example.configuration;

import com.getyourguide.openapi.validation.api.log.LoggerExtension;
import com.getyourguide.openapi.validation.api.metrics.MetricsReporter;
import com.getyourguide.openapi.validation.api.metrics.client.MetricsClient;
import com.getyourguide.openapi.validation.example.logging.ExampleLoggerExtension;
import com.getyourguide.openapi.validation.metrics.LoggingMetricsReporter;
import com.getyourguide.openapi.validation.metrics.client.LoggingMetricsClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ExampleConfiguration {
@Bean
public MetricsReporter metricsReporter() {
return new LoggingMetricsReporter();
public MetricsClient metricsClient() {
return new LoggingMetricsClient();
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.getyourguide.openapi.validation.metrics.datadog.autoconfigure;

import com.getyourguide.openapi.validation.api.metrics.MetricsReporter;
import com.getyourguide.openapi.validation.metrics.datadog.StatsDClientMetricsReporter;
import com.getyourguide.openapi.validation.api.metrics.client.MetricsClient;
import com.getyourguide.openapi.validation.metrics.datadog.client.StatsDClientMetricsClient;
import com.timgroup.statsd.StatsDClient;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
Expand All @@ -18,8 +18,8 @@ public class FallbackLibraryAutoConfiguration {
@Bean
@ConditionalOnBean(StatsDClient.class)
@ConditionalOnMissingBean
public MetricsReporter metricsReporterStatsDClient(StatsDClient statsDClient) {
return new StatsDClientMetricsReporter(statsDClient);
public MetricsClient metricsClientStatsDClient(StatsDClient statsDClient) {
return new StatsDClientMetricsClient(statsDClient);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import static com.getyourguide.openapi.validation.metrics.datadog.OpenApiValidationDataDogMetricsApplicationProperties.PROPERTY_STATSD_SERVICE_HOST;
import static com.getyourguide.openapi.validation.metrics.datadog.OpenApiValidationDataDogMetricsApplicationProperties.PROPERTY_STATSD_SERVICE_PORT;

import com.getyourguide.openapi.validation.api.metrics.MetricsReporter;
import com.getyourguide.openapi.validation.api.metrics.client.MetricsClient;
import com.getyourguide.openapi.validation.metrics.datadog.OpenApiValidationDataDogMetricsApplicationProperties;
import com.getyourguide.openapi.validation.metrics.datadog.StatsDClientMetricsReporter;
import com.getyourguide.openapi.validation.metrics.datadog.client.StatsDClientMetricsClient;
import com.timgroup.statsd.NonBlockingStatsDClientBuilder;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
Expand All @@ -23,12 +23,12 @@ public class LibraryAutoConfiguration {
@Bean
@ConditionalOnClass(name = "com.timgroup.statsd.StatsDClient")
@ConditionalOnProperty({PROPERTY_STATSD_SERVICE_HOST, PROPERTY_STATSD_SERVICE_PORT})
public MetricsReporter metricsReporterCustomStatsDClient() {
public MetricsClient metricsClientCustomStatsDClient() {
var statsDClient = new NonBlockingStatsDClientBuilder()
.prefix(properties.getMetricPrefix())
.hostname(properties.getStatsd().getService().getHost())
.port(properties.getStatsd().getService().getPort())
.build();
return new StatsDClientMetricsReporter(statsDClient);
return new StatsDClientMetricsClient(statsDClient);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

import com.getyourguide.openapi.validation.api.metrics.MetricsReporter;
import com.getyourguide.openapi.validation.metrics.datadog.StatsDClientMetricsReporter;
import com.getyourguide.openapi.validation.api.metrics.client.MetricsClient;
import com.getyourguide.openapi.validation.metrics.datadog.client.StatsDClientMetricsClient;
import com.timgroup.statsd.StatsDClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -24,23 +24,23 @@ void setUp() {
}

@Test
void withDataDogPropertiesShouldCreateStatsDClientMetricsReporter() {
void withDataDogPropertiesShouldCreateStatsDClientMetricsClient() {
contextRunner
.withPropertyValues(
"openapi.validation.datadog.statsd.service.host=localhost",
"openapi.validation.datadog.statsd.service.port=8125"
)
.run(context -> {
assertThat(context)
.hasSingleBean(MetricsReporter.class);
assertThat(context.getBeansOfType(MetricsReporter.class))
.extractingByKey("metricsReporterCustomStatsDClient")
.isInstanceOf(StatsDClientMetricsReporter.class);
.hasSingleBean(MetricsClient.class);
assertThat(context.getBeansOfType(MetricsClient.class))
.extractingByKey("metricsClientCustomStatsDClient")
.isInstanceOf(StatsDClientMetricsClient.class);
});
}

@Test
void withStatsDClientPropertiesAndExistingStatsDClientShouldCreateNewStatsDClientAndCreateStatsDClientMetricsReporter() {
void withStatsDClientPropertiesAndExistingStatsDClientShouldCreateNewStatsDClientAndCreateStatsDClientMetricsClient() {
contextRunner
.withPropertyValues(
"openapi.validation.datadog.statsd.service.host=localhost",
Expand All @@ -49,10 +49,10 @@ void withStatsDClientPropertiesAndExistingStatsDClientShouldCreateNewStatsDClien
.withBean(StatsDClient.class, () -> mock(StatsDClient.class))
.run(context -> {
assertThat(context)
.hasSingleBean(MetricsReporter.class);
assertThat(context.getBeansOfType(MetricsReporter.class))
.extractingByKey("metricsReporterCustomStatsDClient")
.isInstanceOf(StatsDClientMetricsReporter.class);
.hasSingleBean(MetricsClient.class);
assertThat(context.getBeansOfType(MetricsClient.class))
.extractingByKey("metricsClientCustomStatsDClient")
.isInstanceOf(StatsDClientMetricsClient.class);
});
}

Expand All @@ -63,10 +63,10 @@ void noPropertiesWithExistingStatsDClientBean() {
.withBean(StatsDClient.class, () -> mock(StatsDClient.class))
.run(context -> {
assertThat(context)
.hasSingleBean(MetricsReporter.class);
assertThat(context.getBeansOfType(MetricsReporter.class))
.extractingByKey("metricsReporterStatsDClient")
.isInstanceOf(StatsDClientMetricsReporter.class);
.hasSingleBean(MetricsClient.class);
assertThat(context.getBeansOfType(MetricsClient.class))
.extractingByKey("metricsClientStatsDClient")
.isInstanceOf(StatsDClientMetricsClient.class);
});
}

Expand All @@ -75,7 +75,7 @@ void noPropertiesWithoutExistingStatsDClientBean() {
contextRunner
.run(context -> {
assertThat(context)
.doesNotHaveBean(MetricsReporter.class);
.doesNotHaveBean(MetricsClient.class);
});
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
package com.getyourguide.openapi.validation.metrics.datadog;
package com.getyourguide.openapi.validation.metrics.datadog.client;

import com.getyourguide.openapi.validation.api.metrics.MetricTag;
import com.getyourguide.openapi.validation.api.metrics.MetricsReporter;
import com.getyourguide.openapi.validation.api.metrics.client.MetricsClient;
import com.timgroup.statsd.StatsDClient;
import java.util.Arrays;
import java.util.Optional;
import lombok.AllArgsConstructor;

public class StatsDClientMetricsReporter implements MetricsReporter {
@AllArgsConstructor
public class StatsDClientMetricsClient implements MetricsClient {
private final StatsDClient statsDClient;

public StatsDClientMetricsReporter(StatsDClient statsDClient) {
this.statsDClient = statsDClient;
}

@Override
public void increment(String aspect, MetricTag... tags) {
statsDClient.increment(aspect, mapTags(tags));
}

private static String[] mapTags(MetricTag[] tags) {
return Optional.of(tags)
.map(nonNullTags -> Arrays.stream(nonNullTags).map(tag -> tag.getKey() + ":" + tag.getValue()).toArray(String[]::new))
.map(nonNullTags ->
Arrays.stream(nonNullTags).map(tag -> tag.getKey() + ":" + tag.getValue()).toArray(String[]::new)
)
.orElse(new String[0]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.getyourguide.openapi.validation.api.metrics;

import com.getyourguide.openapi.validation.api.metrics.client.MetricsClient;
import com.getyourguide.openapi.validation.api.model.OpenApiViolation;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@AllArgsConstructor
public class DefaultMetricsReporter implements MetricsReporter {

private final MetricsClient metricsClient;
private final Configuration configuration;

@Override
public void reportViolation(OpenApiViolation violation) {
var violationMetricName = configuration.getMetricName() + ".error";
metricsClient.increment(violationMetricName, createTagsForViolation(violation));
}

private MetricTag[] createTagsForViolation(OpenApiViolation violation) {
var tags = new ArrayList<MetricTag>();

tags.add(new MetricTag("type", violation.getDirection().toString().toLowerCase()));
tags.add(new MetricTag("method", violation.getRequestMetaData().getMethod().toLowerCase()));
violation.getNormalizedPath().ifPresent(path -> tags.add(new MetricTag("path", path)));
violation.getResponseStatus()
.ifPresent(responseStatus -> tags.add(new MetricTag("status", responseStatus.toString())));

if (configuration.getMetricAdditionalTags() != null) {
tags.addAll(configuration.getMetricAdditionalTags());
}

return tags.toArray(MetricTag[]::new);
}

@Builder
@Getter
public static class Configuration {
private final String metricName;
private final List<MetricTag> metricAdditionalTags;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.getyourguide.openapi.validation.api.metrics;

import com.getyourguide.openapi.validation.api.model.OpenApiViolation;

public interface MetricsReporter {
void increment(String aspect, MetricTag... tags);
void reportViolation(OpenApiViolation violation);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.getyourguide.openapi.validation.api.metrics.client;

import com.getyourguide.openapi.validation.api.metrics.MetricTag;

public interface MetricsClient {
void increment(String aspect, MetricTag... tags);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.getyourguide.openapi.validation.api.metrics.client;

import com.getyourguide.openapi.validation.api.metrics.MetricTag;

public class NoOpMetricsClient implements MetricsClient {
@Override
public void increment(String aspect, MetricTag... tags) {
// no-op
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,20 @@
import com.getyourguide.openapi.validation.api.exclusions.ViolationExclusions;
import com.getyourguide.openapi.validation.api.log.LogLevel;
import com.getyourguide.openapi.validation.api.log.ViolationLogger;
import com.getyourguide.openapi.validation.api.metrics.MetricTag;
import com.getyourguide.openapi.validation.api.metrics.MetricsReporter;
import com.getyourguide.openapi.validation.api.model.Direction;
import com.getyourguide.openapi.validation.api.model.OpenApiViolation;
import com.getyourguide.openapi.validation.api.model.RequestMetaData;
import com.getyourguide.openapi.validation.core.throttle.ValidationReportThrottler;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@AllArgsConstructor
public class ValidationReportHandler {
private final ValidationReportThrottler throttleHelper;
private final ViolationLogger logger;
private final MetricsReporter metrics;
private final ViolationExclusions violationExclusions;
private final Configuration configuration;

public void handleValidationReport(
RequestMetaData request,
Expand All @@ -43,7 +37,7 @@ public void handleValidationReport(

private void logValidationError(OpenApiViolation openApiViolation) {
logger.log(openApiViolation);
metrics.increment(configuration.getMetricName(), createTags(openApiViolation));
metrics.reportViolation(openApiViolation);
}

private OpenApiViolation buildOpenApiViolation(
Expand Down Expand Up @@ -88,22 +82,6 @@ private boolean isViolationExcluded(OpenApiViolation openApiViolation) {
".*\\[Path '[^']+'] Instance failed to match exactly one schema \\(matched [1-9][0-9]* out of \\d\\).*");
}

private MetricTag[] createTags(OpenApiViolation openApiViolation) {
var tags = new ArrayList<MetricTag>();

tags.add(new MetricTag("type", openApiViolation.getDirection().toString().toLowerCase()));
tags.add(new MetricTag("method", openApiViolation.getRequestMetaData().getMethod().toLowerCase()));
openApiViolation.getNormalizedPath().ifPresent(path -> tags.add(new MetricTag("path", path)));
openApiViolation.getResponseStatus()
.ifPresent(responseStatus -> tags.add(new MetricTag("status", responseStatus.toString())));

if (configuration.getMetricAdditionalTags() != null) {
tags.addAll(configuration.getMetricAdditionalTags());
}

return tags.toArray(MetricTag[]::new);
}

private static Optional<String> getPointersInstance(ValidationReport.Message message) {
return message.getContext()
.flatMap(ValidationReport.MessageContext::getPointers)
Expand Down Expand Up @@ -144,11 +122,4 @@ private LogLevel mapLogLevel(ValidationReport.Level level) {
case IGNORE -> LogLevel.IGNORE;
};
}

@Builder
@Getter
public static class Configuration {
private final String metricName;
private final List<MetricTag> metricAdditionalTags;
}
}
Loading