Search code examples
loggingrequestinterceptorfeign

Feign Client request and response and URL Logging


How I can log the payload of Feign client request, response and URL. do I have to Implement an Interceptor? Because my requirement is logging the request and response on a special table on the database.


Solution

  • Feign has out of box logging mechanism and it can be achieved through simple steps.

    If you are using spring-cloud-starter-feign

    Feign using Slf4jLogger for logging.Feign logging documentation

    As per doc, the below logging levels are available to configure,

    • NONE - No logging (DEFAULT).
    • BASIC - Log only the request method and URL and the response status code and execution time.
    • HEADERS - Log the basic information along with request and response headers.
    • FULL - Log the headers, body, and metadata for both requests and responses.

    Injecting the Logger.Level bean is enough.

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

    OR

    If you prefer using configuration properties to configured all @FeignClient, you can create configuration properties with default feign name.

    feign:
      client:
        config:
          default:
            loggerLevel: basic
    

    of if you are using a newer version (Spring Boot 3) the config will be like:

    spring:
      cloud:
        openfeign:
          client:
            config:
              default:
                loggerLevel: full
    

    If you are using 'io.github.openfeign:feign-core'

    If you are constructing the Feign builder then you can mention logLevel(Level.BASIC) as

    Feign.builder()
        .logger(new Slf4jLogger())
        .logLevel(Level.BASIC)
        .target(SomeFeignClient.class, url);
    

    We have the flexibility to customize the logging message

    The default feign request and response logging

    Request logging

    Resopnse logging

    we can customize the feign request, response logging pattern by overriding Logger#logRequest and Logger#logAndRebufferResponse methods. In the following example, we have customized request logging pattern

    log(configKey, "---> %s %s HTTP/1.1 (%s-byte body) ", request.httpMethod().name(), request.url(), bodyLength);
    

    and response logging pattern

    log(configKey, "<--- %s %s HTTP/1.1 %s (%sms) ", request.httpMethod().name(), request.url(), status, elapsedTime);
    

    The Full example is

    
    import feign.Logger;
    import feign.Request;
    import feign.Response;
    import lombok.extern.slf4j.Slf4j;
    
    import java.io.IOException;
    
    import static feign.Logger.Level.HEADERS;
    
    @Slf4j
    public class CustomFeignRequestLogging extends Logger {
    
        @Override
        protected void logRequest(String configKey, Level logLevel, Request request) {
    
            if (logLevel.ordinal() >= HEADERS.ordinal()) {
                super.logRequest(configKey, logLevel, request);
            } else {
                int bodyLength = 0;
                if (request.requestBody().asBytes() != null) {
                    bodyLength = request.requestBody().asBytes().length;
                }
                log(configKey, "---> %s %s HTTP/1.1 (%s-byte body) ", request.httpMethod().name(), request.url(), bodyLength);
            }
        }
    
        @Override
        protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime)
                throws IOException {
            if (logLevel.ordinal() >= HEADERS.ordinal()) {
                return super.logAndRebufferResponse(configKey, logLevel, response, elapsedTime);
            } else {
                int status = response.status();
                Request request = response.request();
                log(configKey, "<--- %s %s HTTP/1.1 %s (%sms) ", request.httpMethod().name(), request.url(), status, elapsedTime);
                return response;   
            }        
        }
    
    
        @Override
        protected void log(String configKey, String format, Object... args) {
            log.debug(format(configKey, format, args));
        }
    
        protected String format(String configKey, String format, Object... args) {
            return String.format(methodTag(configKey) + format, args);
        }
    }
    
    

    NOTE: Request payload can be easily logged through

    String bodyText =
                  request.charset() != null ? new String(request.body(), request.charset()) : null;
    

    but be careful writing the response payload after you are reading the input stream Util.toByteArray(response.body().asInputStream()) then you have to construct the response again like response.toBuilder().body(bodyData).build(). Otherwise, you will end up with the expection. The reason is response streams are read and always closed before returning, thats why the method is named as logAndRebufferResponse

    How to use the custom CustomFeignRequestLogging?

    If you are building feign client using just 'io.github.openfeign:feign-core'

    Feign.builder()
         .logger(new CustomFeignRequestLogging())
         .logLevel(feign.Logger.Level.BASIC);
    
    

    If you are using 'org.springframework.cloud:spring-cloud-starter-openfeign'

    @Configuration
    public class FeignLoggingConfiguration {
    
        @Bean
        public CustomFeignRequestLogging customFeignRequestLogging() {
            return new CustomFeignRequestLogging();
        }
    
        @Bean
        Logger.Level feignLoggerLevel() {
            return Logger.Level.BASIC;
        }
    }