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.
6 Answers
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
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
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;
}
}

- 1
- 1

- 4,570
- 2
- 42
- 59
-
1Thank you for detailed information, it is helpful for me. – Haven Lin Mar 03 '21 at 01:59
-
How do you plug the CustomFeignRequestLogging to feign config? – Manoj Kumar S Aug 05 '21 at 12:28
-
1@ManojKumarS, good question, I have updated the answer stating How to use the custom CustomFeignRequestLogging? – Prasanth Rajendran Aug 05 '21 at 13:26
-
2This is a very useful answer. I just wanted to add clarity on the part that requires being careful when there is a need of writing the response. Reconstructing the response would look like this: `ByteArray bodyData = Util.toByteArray(response.body().asInputStream())` `Response responseCopy = response.toBuilder().body(bodyData).build()` With this, you can keep using `responseCopy` as desired. – Sam S Dec 02 '22 at 10:18
The accepted answer did not work for me until I added the following settings to my application.yml file:
logging:
level:
com:
mypackage1:
mysubackage1:
mysubpackage2: DEBUG

- 384
- 3
- 11
I am using Feign client builder as follows
@Bean
public VpsFeignClient vpsFeignClient() {
return Feign.builder()
.encoder(new FormEncoder(new GsonEncoder()))
.decoder(new GsonDecoder())
.logger(new Slf4jLogger(VpsFeignClient.class))
.logLevel(feignLoggerLevel())
.retryer(new Default())
.errorDecoder(new CustomServerErrorDecoder())
.requestInterceptor(template -> {
//Set some header if necessary
template.header("Content-Type", "application/json");
})
.contract(new SpringMvcContract())
.target(VpsFeignClient.class, dataVpsEndpoint);
}
And my feign is
public interface VpsFeignClient {
@RequestMapping(path = "/test", method = RequestMethod.GET)
TimeRespDto getTestValue();
}

- 15,907
- 4
- 25
- 31

- 311
- 2
- 11
-
When using `@Slf4j` the line `.logger(new Slf4jLogger(VpsFeignClient.class))` did the trick for me. Now I'm able to see the Feign Client logs. – dsicari Jun 04 '23 at 23:35
Feign provides a Logger
interface that can log the full Request and Response. You will need to set the Logger.Level
in the Feign Builder or Configuration.
Feign.builder()
.logLevel(Logger.Level.FULL) // this will log the request and response
.target(MyApi, "my host");

- 1,193
- 8
- 14
-
Thank you Kevin. Yes, you right but I want to log the request and response on special table on the database. – Rami Nassar May 26 '19 at 09:11
-
@RamiNassar, That was not in your original question. I recommend either updating your original question with that information or asking another question being more specific. – Kevin Davis May 28 '19 at 16:12
in your RestConfiguration you need to up default level of logging feignClient and override by @Bean feignLogger like:
@Configuration(proxyBeanMethods = false)
@EnableCircuitBreaker
@EnableFeignClients(basePackageClasses = [Application::class])
class RestConfiguration: WebMvcConfigurer {
@Bean
fun feignLoggerLevel(): Logger.Level {
return Logger.Level.FULL
}
@Bean
fun feignLogger(): Logger {
return FeignClientLogger()
}
}
and implement your logger as you want. For example logging in logbook format:
import feign.Logger
import feign.Request
import feign.Response
import feign.Util.*
import org.slf4j.LoggerFactory
class FeignClientLogger : Logger() {
private val log = LoggerFactory.getLogger(this::class.java)
override fun logRequest(configKey: String?, logLevel: Level?, request: Request?) {
if (request == null)
return
val feignRequest = FeignRequest()
feignRequest.method = request.httpMethod().name
feignRequest.url = request.url()
for (field in request.headers().keys) {
for (value in valuesOrEmpty(request.headers(), field)) {
feignRequest.addHeader(field, value)
}
}
if (request.requestBody() != null) {
feignRequest.body = request.requestBody().asString()
}
log.trace(feignRequest.toString())
}
override fun logAndRebufferResponse(
configKey: String?,
logLevel: Level?,
response: Response?,
elapsedTime: Long
): Response? {
if (response == null)
return response
val feignResponse = FeignResponse()
val status = response.status()
feignResponse.status = response.status()
feignResponse.reason =
(if (response.reason() != null && logLevel!! > Level.NONE) " " + response.reason() else "")
feignResponse.duration = elapsedTime
if (logLevel!!.ordinal >= Level.HEADERS.ordinal) {
for (field in response.headers().keys) {
for (value in valuesOrEmpty(response.headers(), field)) {
feignResponse.addHeader(field, value)
}
}
if (response.body() != null && !(status == 204 || status == 205)) {
val bodyData: ByteArray = toByteArray(response.body().asInputStream())
if (logLevel.ordinal >= Level.FULL.ordinal && bodyData.isNotEmpty()) {
feignResponse.body = decodeOrDefault(bodyData, UTF_8, "Binary data")
}
log.trace(feignResponse.toString())
return response.toBuilder().body(bodyData).build()
} else {
log.trace(feignResponse.toString())
}
}
return response
}
override fun log(p0: String?, p1: String?, vararg p2: Any?) {}
}
class FeignResponse {
var status = 0
var reason: String? = null
var duration: Long = 0
private val headers: MutableList<String> = mutableListOf()
var body: String? = null
fun addHeader(key: String?, value: String?) {
headers.add("$key: $value")
}
override fun toString() =
"""{"type":"response","status":"$status","duration":"$duration","headers":$headers,"body":$body,"reason":"$reason"}"""
}
class FeignRequest {
var method: String? = null
var url: String? = null
private val headers: MutableList<String> = mutableListOf()
var body: String? = null
fun addHeader(key: String?, value: String?) {
headers.add("$key: $value")
}
override fun toString() =
"""{"type":"request","method":"$method","url":"$url","headers":$headers,"body":$body}"""
}
There is no interceptor for Feign client response. The request interceptor the only available for Feign client.
The best solution will be by using RestTemplate rather than Feign:
@Configuration
public class RestConfiguration {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate
= new RestTemplate(
new BufferingClientHttpRequestFactory(
new SimpleClientHttpRequestFactory()
)
);
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new UserRestTemplateClientInterceptor());
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
And the @Autowire the restTemplate where you want to use as the following:
@Autowire
RestTemplate restTemplate;

- 303
- 1
- 4
- 13
-
The question specifically asks for a feign client implementation, not a REST template – mnagdev Nov 21 '22 at 13:01