Skip to main content
  1. Articles/

Custom Logger Configuration for Feign Clients

·3 mins
Mayukh Datta
Technical Spring Boot

Feign Client allows us to abstract the mechanics of calling a REST service. We need to configure and annotate the Feign Client interface to call a RESTful web service by making a simple Java method call.

@FeignClient(name = "UserService", url = "${userService.base_url}")
public interface UserServiceClient {
  @GetMapping("/users")
  ResponseEntity<UserServiceResponse> getUsers();
}

Feign provides an out-of-the-box solution to log the outgoing requests and the following responses. Read this article to know how to do the configuration for it.

Feign uses SL4j Logger under the hood, and the default logging configuration prints each piece of information on a separate line, which I found disturbing. So, I created a custom logger for the Feign clients to customize the log statements.

Before the custom logger configuration

Creating our own Custom Logger #

We create a configuration class in Spring Boot that instantiates a bean of our custom logger class. The custom logger extends the original Logger implementation and overrides the necessary methods.

@Slf4j
@Configuration
public class FeignConfig {

  @Bean
  public CustomFeignClientLogger customFeignClientLogger() {
    return new CustomFeignClientLogger();
  }

  @Bean
  Logger.Level feignLoggerLevel() {
    return Logger.Level.FULL;
  }

  static class CustomFeignClientLogger extends Logger {

    @Override
    protected void logRequest(String configKey, Level logLevel, Request request) {
      /*
       * This method is intentionally left blank to prevent it
       * from logging the request. We will log both the request and the response
       * in the logAndRebufferResponse method.
       */
    }

    @Override
    protected Response logAndRebufferResponse(
        String configKey, Level logLevel, Response response, long elapsedTime) {
      int status = response.status();
      Request request = response.request();
      String responseString = "";
      Response clonedResponse = response.toBuilder().build();

      if (Objects.nonNull(response.body())) {
        try {
          byte[] bodyData = Util.toByteArray(response.body().asInputStream());
          Response.Body responseBodyCopy = response.toBuilder().body(bodyData).build().body();
          clonedResponse = response.toBuilder().body(responseBodyCopy).build();
          responseString = StringUtil.toString(responseBodyCopy.asReader(StandardCharsets.UTF_8));
        } catch (IOException exception) {
          log.error(
              "Error while reading the Feign client response || Error Details: {}",
              exception.getMessage());
        }
      }

      if (Objects.nonNull(request.body())) {
        log(
            configKey,
            "%n%n---> %s %s %nRequest: %s %n %n<--- %s %s %s (%sms) %nResponse: %s",
            request.httpMethod().name(),
            request.url(),
            new String(request.body()),
            request.httpMethod().name(),
            request.url(),
            status,
            elapsedTime,
            responseString);
      }

      return clonedResponse;
    }

    @Override
    protected void log(String configKey, String format, Object... args) {
      log.info(format(configKey, format, args));
    }

    protected String format(String configKey, String format, Object... args) {
      String methodTag = methodTag(configKey);
      String formattedString = String.format(format, args);
      return methodTag + formattedString;
    }
  }
}

We read the Response.body as an InputStream. Response streams are read and always closed before it gets returned. So, we have to clone the whole Response object and return the cloned response from the logAndRebufferResponse method so that we don’t encounter a ‘stream is closed’ IOException later.

If we annotate the class with the @Configuration annotation then this custom logger configuration will be picked up by all the Feign Clients in our service.

Here is how the feign log statements look now.

After the custom logger configuration