From 4f68a186b03740df15114feea225c0faff7dd80f Mon Sep 17 00:00:00 2001 From: Vladimir Orany Date: Fri, 24 May 2024 12:28:34 +0200 Subject: [PATCH] customizable logging and fixed circular dependency in bean injection --- .../CriticalNewRelicInsightsClient.java | 38 ++++++----------- .../DefaultNewRelicInsightsService.java | 35 +++++++++++++++- .../newrelic/NewRelicConfiguration.java | 12 +++--- .../newrelic/NewRelicRetryPredicate.java | 41 +++++++++++++++++++ 4 files changed, 93 insertions(+), 33 deletions(-) create mode 100644 libs/micronaut-newrelic/src/main/java/com/agorapulse/micronaut/newrelic/NewRelicRetryPredicate.java diff --git a/libs/micronaut-newrelic/src/main/java/com/agorapulse/micronaut/newrelic/CriticalNewRelicInsightsClient.java b/libs/micronaut-newrelic/src/main/java/com/agorapulse/micronaut/newrelic/CriticalNewRelicInsightsClient.java index ecc4072..70fbf94 100644 --- a/libs/micronaut-newrelic/src/main/java/com/agorapulse/micronaut/newrelic/CriticalNewRelicInsightsClient.java +++ b/libs/micronaut-newrelic/src/main/java/com/agorapulse/micronaut/newrelic/CriticalNewRelicInsightsClient.java @@ -1,41 +1,29 @@ package com.agorapulse.micronaut.newrelic; -import io.micronaut.core.annotation.Nullable; +import io.micronaut.context.annotation.Requires; import io.micronaut.retry.annotation.Retryable; -import jakarta.inject.Named; import jakarta.inject.Singleton; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.Map; +/** + * This client is used to send critical events to New Relic Insights. + * + * This class does not implement the {@link NewRelicInsightsClient} interface to avoid cyclic dependencies. + */ @Singleton -@Named("critical") -public class CriticalNewRelicInsightsClient implements NewRelicInsightsClient { +@Requires(bean = NewRelicInsightsClient.class) +public class CriticalNewRelicInsightsClient { - private static final Logger LOGGER = LoggerFactory.getLogger(CriticalNewRelicInsightsClient.class); + private final NewRelicInsightsClient client; - private final NewRelicInsightsServiceClient serviceClient; - private final NewRelicInsightsUrlClient urlClient; - - public CriticalNewRelicInsightsClient( - @Nullable NewRelicInsightsServiceClient serviceClient, - @Nullable NewRelicInsightsUrlClient urlClient - ) { - this.serviceClient = serviceClient; - this.urlClient = urlClient; + public CriticalNewRelicInsightsClient(NewRelicInsightsClient client) { + this.client = client; } - @Override - @Retryable(attempts = "${newrelic.retry.counts:3}") + @Retryable(attempts = "${newrelic.retry.count:3}", predicate = NewRelicRetryPredicate.class) public void createEvents(Iterable> events) { - if (serviceClient != null) { - serviceClient.createEvents(events); - } else if (urlClient != null) { - urlClient.createEvents(events); - } else { - LOGGER.error("No New Relic client found to send events: {}", events); - } + client.createEvents(events); } } diff --git a/libs/micronaut-newrelic/src/main/java/com/agorapulse/micronaut/newrelic/DefaultNewRelicInsightsService.java b/libs/micronaut-newrelic/src/main/java/com/agorapulse/micronaut/newrelic/DefaultNewRelicInsightsService.java index 096136a..5f2e073 100644 --- a/libs/micronaut-newrelic/src/main/java/com/agorapulse/micronaut/newrelic/DefaultNewRelicInsightsService.java +++ b/libs/micronaut-newrelic/src/main/java/com/agorapulse/micronaut/newrelic/DefaultNewRelicInsightsService.java @@ -31,7 +31,6 @@ import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; /** * Default NewRelicInsightsService, sends events to the New Relic API in real time, with a blocking request. @@ -45,6 +44,7 @@ public class DefaultNewRelicInsightsService implements NewRelicInsightsService { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultNewRelicInsightsService.class); + private static final String DEFAULT_ERROR_MESSAGE = "Exception creating New Relic events "; private final NewRelicInsightsClient criticalClient; private final NewRelicInsightsClient client; @@ -70,7 +70,18 @@ public void createEvents(@Valid @NonNull Collection events) { // keep the validation exceptions throw cve; } catch (Exception ex) { - LoggerFactory.getLogger(getClass()).error("Exception creating New Relic events " + ex); + boolean hasCriticalEvents = events.stream() + .map(extractor::extractPayload) + .anyMatch(EventPayloadExtractor::isCritical); + + if (hasCriticalEvents) { + log(ex); + } else { + // only log events that won't trigger retry with critical client + if (!NewRelicRetryPredicate.INSTANCE.test(ex)) { + log(ex); + } + } } } @@ -95,4 +106,24 @@ public void unsafeCreateEvents(@NonNull @Valid Collection events) { } } + private void log(Exception ex) { + switch (configuration.getLogLevel()) { + case TRACE: + LOGGER.trace(DEFAULT_ERROR_MESSAGE, ex); + break; + case DEBUG: + LOGGER.debug(DEFAULT_ERROR_MESSAGE, ex); + break; + case INFO: + LOGGER.info(DEFAULT_ERROR_MESSAGE, ex); + break; + case ERROR: + LOGGER.error(DEFAULT_ERROR_MESSAGE, ex); + break; + case WARN: + default: + LOGGER.warn(DEFAULT_ERROR_MESSAGE, ex); + } + } + } diff --git a/libs/micronaut-newrelic/src/main/java/com/agorapulse/micronaut/newrelic/NewRelicConfiguration.java b/libs/micronaut-newrelic/src/main/java/com/agorapulse/micronaut/newrelic/NewRelicConfiguration.java index f2b2a5a..6ce0020 100644 --- a/libs/micronaut-newrelic/src/main/java/com/agorapulse/micronaut/newrelic/NewRelicConfiguration.java +++ b/libs/micronaut-newrelic/src/main/java/com/agorapulse/micronaut/newrelic/NewRelicConfiguration.java @@ -20,6 +20,7 @@ import io.micronaut.context.annotation.ConfigurationProperties; import io.micronaut.core.annotation.Nullable; +import org.slf4j.event.Level; @ConfigurationProperties("newrelic") public class NewRelicConfiguration { @@ -27,7 +28,7 @@ public class NewRelicConfiguration { @Nullable private String url; @Nullable private String token; - private boolean logErrors = true; + private Level logLevel = Level.WARN; @Nullable public String getUrl() { return url; @@ -45,12 +46,11 @@ public void setToken(@Nullable String token) { this.token = token; } - public boolean isLogErrors() { - return logErrors; + public Level getLogLevel() { + return logLevel; } - public void setLogErrors(boolean logErrors) { - this.logErrors = logErrors; + public void setLogLevel(Level logLevel) { + this.logLevel = logLevel; } - } diff --git a/libs/micronaut-newrelic/src/main/java/com/agorapulse/micronaut/newrelic/NewRelicRetryPredicate.java b/libs/micronaut-newrelic/src/main/java/com/agorapulse/micronaut/newrelic/NewRelicRetryPredicate.java new file mode 100644 index 0000000..898c5fd --- /dev/null +++ b/libs/micronaut-newrelic/src/main/java/com/agorapulse/micronaut/newrelic/NewRelicRetryPredicate.java @@ -0,0 +1,41 @@ +package com.agorapulse.micronaut.newrelic; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.http.client.exceptions.HttpClientException; +import io.micronaut.http.client.exceptions.ReadTimeoutException; +import io.micronaut.retry.annotation.RetryPredicate; + +import java.util.List; +import java.util.function.Predicate; + +@Introspected +public class NewRelicRetryPredicate implements RetryPredicate { + + public static Predicate INSTANCE = new NewRelicRetryPredicate(); + + private static final List RETRYABLE_MESSAGES = List.of( + "request failed", + "connection refused", + "connection reset", + "connection timed out", + "no route to host", + "read timed out", + "write timed out" + ); + + @Override + public boolean test(Throwable throwable) { + if (!(throwable instanceof HttpClientException)) { + return false; + } + + if (throwable instanceof ReadTimeoutException) { + return true; + } + + String message = throwable.getMessage() == null ? "" : throwable.getMessage().toLowerCase(); + + return RETRYABLE_MESSAGES.stream().anyMatch(message::contains); + } + +}