Feign 客户端请求和响应以及 URL 日志记录
Feign Client request and response and URL Logging
如何记录 Feign 客户端请求、响应和 URL 的负载。我必须实施拦截器吗?因为我的要求是在数据库的特殊 table 上记录请求和响应。
Feign 客户端响应没有拦截器。 Feign客户端唯一可用的请求拦截器。
最好的解决方案是使用 RestTemplate 而不是 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;
}
}
和@Autowire restTemplate 你想使用的地方如下:
@Autowire
RestTemplate restTemplate;
Feign 提供了一个 Logger
接口,可以记录完整的请求和响应。您需要在 Feign Builder 或配置中设置 Logger.Level
。
Feign.builder()
.logLevel(Logger.Level.FULL) // this will log the request and response
.target(MyApi, "my host");
Feign 具有开箱即用的日志记录机制,可以通过简单的步骤实现。
如果您使用的是spring-cloud-starter-feign
假装使用 Slf4jLogger
进行日志记录。Feign logging documentation
根据文档,可以配置以下日志记录级别,
NONE
- 无日志记录(默认)。
BASIC
- 仅记录请求方法和URL以及响应状态代码和执行时间。
HEADERS
- 记录基本信息以及请求和响应 headers。
FULL
- 记录请求和响应的 headers、body 和元数据。
注入 Logger.Level
bean 就足够了。
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.BASIC;
}
或
如果您更喜欢使用配置属性而不是全部配置 @FeignClient
,您可以使用默认的伪装名称创建配置属性。
feign:
client:
config:
default:
loggerLevel: basic
如果您使用的是'io.github.openfeign:feign-core'
如果您正在构建 Feign 生成器,那么您可以提及 logLevel(Level.BASIC)
为
Feign.builder()
.logger(new Slf4jLogger())
.logLevel(Level.BASIC)
.target(SomeFeignClient.class, url);
我们可以灵活地自定义日志消息
默认的feign请求和响应日志记录
我们可以通过覆盖 Logger#logRequest
和 Logger#logAndRebufferResponse
方法来自定义假请求、响应日志记录模式。在下面的示例中,我们自定义了请求日志记录模式
log(configKey, "---> %s %s HTTP/1.1 (%s-byte body) ", request.httpMethod().name(), request.url(), bodyLength);
和响应记录模式
log(configKey, "<--- %s %s HTTP/1.1 %s (%sms) ", request.httpMethod().name(), request.url(), status, elapsedTime);
完整示例是
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()) {
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);
}
}
注意:
可以通过
轻松记录请求负载
String bodyText =
request.charset() != null ? new String(request.body(), request.charset()) : null;
但是在读取输入流后要小心写入响应负载 Util.toByteArray(response.body().asInputStream())
然后你必须像 response.toBuilder().body(bodyData).build()
一样再次构造响应。否则,您将以期望结束。原因是响应流在返回之前被读取并总是关闭,这就是为什么该方法被命名为 logAndRebufferResponse
如何使用自定义CustomFeignRequestLogging
?
如果您只使用 'io.github.openfeign:feign-core'
构建假客户端
Feign.builder()
.logger(new CustomFeignRequestLogging())
.logLevel(feign.Logger.Level.BASIC);
如果您正在使用 '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;
}
}
在您的 RestConfiguration 中,您需要设置默认的日志记录级别 feignClient 并由 @Bean feignLogger 覆盖,例如:
@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()
}
}
并根据需要实现您的记录器。例如以日志格式登录:
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}"""
}
如何记录 Feign 客户端请求、响应和 URL 的负载。我必须实施拦截器吗?因为我的要求是在数据库的特殊 table 上记录请求和响应。
Feign 客户端响应没有拦截器。 Feign客户端唯一可用的请求拦截器。
最好的解决方案是使用 RestTemplate 而不是 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;
}
}
和@Autowire restTemplate 你想使用的地方如下:
@Autowire
RestTemplate restTemplate;
Feign 提供了一个 Logger
接口,可以记录完整的请求和响应。您需要在 Feign Builder 或配置中设置 Logger.Level
。
Feign.builder()
.logLevel(Logger.Level.FULL) // this will log the request and response
.target(MyApi, "my host");
Feign 具有开箱即用的日志记录机制,可以通过简单的步骤实现。
如果您使用的是spring-cloud-starter-feign
假装使用 Slf4jLogger
进行日志记录。Feign logging documentation
根据文档,可以配置以下日志记录级别,
NONE
- 无日志记录(默认)。BASIC
- 仅记录请求方法和URL以及响应状态代码和执行时间。HEADERS
- 记录基本信息以及请求和响应 headers。FULL
- 记录请求和响应的 headers、body 和元数据。
注入 Logger.Level
bean 就足够了。
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.BASIC;
}
或
如果您更喜欢使用配置属性而不是全部配置 @FeignClient
,您可以使用默认的伪装名称创建配置属性。
feign:
client:
config:
default:
loggerLevel: basic
如果您使用的是'io.github.openfeign:feign-core'
如果您正在构建 Feign 生成器,那么您可以提及 logLevel(Level.BASIC)
为
Feign.builder()
.logger(new Slf4jLogger())
.logLevel(Level.BASIC)
.target(SomeFeignClient.class, url);
我们可以灵活地自定义日志消息
默认的feign请求和响应日志记录
我们可以通过覆盖 Logger#logRequest
和 Logger#logAndRebufferResponse
方法来自定义假请求、响应日志记录模式。在下面的示例中,我们自定义了请求日志记录模式
log(configKey, "---> %s %s HTTP/1.1 (%s-byte body) ", request.httpMethod().name(), request.url(), bodyLength);
和响应记录模式
log(configKey, "<--- %s %s HTTP/1.1 %s (%sms) ", request.httpMethod().name(), request.url(), status, elapsedTime);
完整示例是
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()) {
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);
}
}
注意: 可以通过
轻松记录请求负载String bodyText =
request.charset() != null ? new String(request.body(), request.charset()) : null;
但是在读取输入流后要小心写入响应负载 Util.toByteArray(response.body().asInputStream())
然后你必须像 response.toBuilder().body(bodyData).build()
一样再次构造响应。否则,您将以期望结束。原因是响应流在返回之前被读取并总是关闭,这就是为什么该方法被命名为 logAndRebufferResponse
如何使用自定义CustomFeignRequestLogging
?
如果您只使用 'io.github.openfeign:feign-core'
Feign.builder()
.logger(new CustomFeignRequestLogging())
.logLevel(feign.Logger.Level.BASIC);
如果您正在使用 '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;
}
}
在您的 RestConfiguration 中,您需要设置默认的日志记录级别 feignClient 并由 @Bean feignLogger 覆盖,例如:
@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()
}
}
并根据需要实现您的记录器。例如以日志格式登录:
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}"""
}