Last active
February 10, 2022 06:19
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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