Skip to content

Instantly share code, notes, and snippets.

@mikybars
Last active February 10, 2022 06:19
Show Gist options
  • Select an option

  • Save mikybars/1ea4ddb04f997d0ec4d4b973e033bfa1 to your computer and use it in GitHub Desktop.

Select an option

Save mikybars/1ea4ddb04f997d0ec4d4b973e033bfa1 to your computer and use it in GitHub Desktop.

Revisions

  1. mikybars revised this gist Feb 10, 2022. No changes.
  2. mikybars created this gist Feb 10, 2022.
    147 changes: 147 additions & 0 deletions RestClientLoggingEndpoint.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,147 @@
    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;
    }
    }
    }
    8 changes: 8 additions & 0 deletions use.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,8 @@
    # list all available rest client beans
    http :8080/actuator/rest-clients
    {
    "exampleRestClient"
    }

    # toggle logging for exampleRestClient
    http POST :8080/actuator/rest-clients/exampleRestClient