Skip to content

Commit

Permalink
customizable logging and fixed circular dependency in bean injection
Browse files Browse the repository at this point in the history
  • Loading branch information
musketyr committed May 24, 2024
1 parent 5920b51 commit 4f68a18
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -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<Map<String, Object>> 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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
Expand All @@ -70,7 +70,18 @@ public <E> void createEvents(@Valid @NonNull Collection<E> 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);
}
}
}
}

Expand All @@ -95,4 +106,24 @@ public <E> void unsafeCreateEvents(@NonNull @Valid Collection<E> 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);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@
import io.micronaut.context.annotation.ConfigurationProperties;

import io.micronaut.core.annotation.Nullable;
import org.slf4j.event.Level;

@ConfigurationProperties("newrelic")
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;
Expand All @@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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<Throwable> INSTANCE = new NewRelicRetryPredicate();

private static final List<String> 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);
}

}

0 comments on commit 4f68a18

Please sign in to comment.