Skip to content

Instantly share code, notes, and snippets.

@mikybars
Last active February 10, 2022 06:19
Show Gist options
  • Save mikybars/1ea4ddb04f997d0ec4d4b973e033bfa1 to your computer and use it in GitHub Desktop.
Save mikybars/1ea4ddb04f997d0ec4d4b973e033bfa1 to your computer and use it in GitHub Desktop.
[Spring Boot Actuator endpoint to enable/disable RestTemplate logging at runtime] Logging is automatically disabled after a certain amount of time to prevent clogging #spring
package com.inditex.mecretcond.infra.actuator;
import static java.time.Instant.now;
import static java.util.stream.Collectors.toList;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
@Slf4j
@Component
@Endpoint(id = "rest-clients")
@RequiredArgsConstructor
public class RestClientLoggingEndpoint {
private static final ClientHttpRequestInterceptor LOGGING_INTERCEPTOR = new LoggingInterceptor();
private static final BufferingClientHttpRequestFactory BUFFERING_CLIENT_HTTP_REQUEST_FACTORY =
new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
private static final Duration MAX_TIME_WITH_LOGGING_ENABLED = Duration.ofMinutes(1L);
private final Map<String, RestTemplate> restClientBeans;
private final Map<String, ClientHttpRequestFactory> httpRequestFactories = new HashMap<>();
private final Map<String, Instant> activationTimes = new HashMap<>();
@Scheduled(fixedDelay = 30 * 1000)
private void disableLoggingAfterAWhile() {
List<String> exceededRestClients = activationTimes.keySet().stream()
.filter(this::hasExceededTimeWithLoggingEnabled)
.collect(toList());
exceededRestClients.forEach(this::disableLogging); // avoid concurrent modification
}
private boolean hasExceededTimeWithLoggingEnabled(String beanName) {
Instant activatedAt = activationTimes.get(beanName);
return Duration.between(activatedAt, now()).compareTo(MAX_TIME_WITH_LOGGING_ENABLED) > 0;
}
@ReadOperation
public List<String> getAvailableRestClients() {
Set<String> beanNames = restClientBeans.keySet();
return new ArrayList<>(beanNames);
}
@WriteOperation
public void toggleLogging(@Selector String beanName) {
if (activationTimes.containsKey(beanName)) {
disableLogging(beanName);
} else {
enableLogging(beanName);
}
}
private void enableLogging(String beanName) {
if (!restClientBeans.containsKey(beanName)) {
return;
}
var restClient = restClientBeans.get(beanName);
backUpHttpRequestFactory(beanName, restClient);
setUpBuffering(restClient);
setUpInterceptor(restClient);
activationTimes.put(beanName, now());
log.debug("Logging enabled for rest client {}", beanName);
}
private void disableLogging(String beanName) {
if (!restClientBeans.containsKey(beanName)) {
return;
}
var restClient = restClientBeans.get(beanName);
restoreHttpRequestFactory(beanName, restClient);
removeInterceptor(restClient);
activationTimes.remove(beanName);
log.debug("Logging disabled for rest client {}", beanName);
}
private void restoreHttpRequestFactory(String beanName, RestTemplate restClient) {
if (httpRequestFactories.containsKey(beanName)) {
restClient.setRequestFactory(httpRequestFactories.get(beanName));
}
}
private void removeInterceptor(RestTemplate restClient) {
restClient.getInterceptors().remove(LOGGING_INTERCEPTOR);
}
private void backUpHttpRequestFactory(String beanName, RestTemplate restClient) {
httpRequestFactories.put(beanName, restClient.getRequestFactory());
}
private void setUpBuffering(RestTemplate restClient) {
restClient.setRequestFactory(BUFFERING_CLIENT_HTTP_REQUEST_FACTORY);
}
private void setUpInterceptor(RestTemplate restClient) {
restClient.getInterceptors().add(LOGGING_INTERCEPTOR);
}
@Slf4j
private static class LoggingInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest req, byte[] reqBody, ClientHttpRequestExecution ex) throws IOException {
log.debug("Request body: {}", new String(reqBody, StandardCharsets.UTF_8));
ClientHttpResponse response = ex.execute(req, reqBody);
InputStreamReader isr = new InputStreamReader(response.getBody(), StandardCharsets.UTF_8);
try (var buffer = new BufferedReader(isr)) {
String body = buffer.lines().collect(Collectors.joining("\n"));
log.debug("Response body: {}", body);
}
return response;
}
}
}
# list all available rest client beans
http :8080/actuator/rest-clients
{
"exampleRestClient"
}
# toggle logging for exampleRestClient
http POST :8080/actuator/rest-clients/exampleRestClient
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment