`
阅读更多

一、背景介绍

spring cloud 微服务架构中,需要对依赖服务的请求留痕(URL,出参,入参、耗时等)

1.现状

调用FeignClient(Integration类中)时 手工记录log

缺点:重复工作、log格式不统一

优点:充分满足个性化需求、灵活度极高(再说上天了)

 

2.目标

实现简洁统一的全局外部服务调用日志输出

示例如下:

get: GET 200 http://wwww.feigntest.com/dict/items?systemId=11 86 RESPONSE BODY:{"code":"000000","success":true}

post: POST 200 http://wwww.feigntest.com/limitBill 85 REQUEST BODY:{"custNo":"121212","startTime":null,"endTime":null} RESPONSE BODY:{"code":"001001010","success":false}

异常: POST UnknownHostException http://wwww.feigntest.com/errorUri 206 REQUEST BODY:{"custNo":"121212"}

3.实现基础

基于FeignClient已有的全局请求日志处理类feign.Logger

feign.Logger日志输出级别为debug

输出header信息较多,大多服务并不需要

 

网上有类似资料,讲解如何启用feign.Logger、如何修改debug为info级别

本文增加对输出内容的简化.

 

二、如何使用

step 1.重写feign.Logger 

-> 修改feign.Logger日志输出级别为info,简化输出内容

package com.cdfinance.mall.config;
import feign.Request;
import feign.Response;
import feign.Util;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static feign.Util.UTF_8;
import static feign.Util.decodeOrDefault;
/**
 * Feign请求日志处理类<br/>
 * 所有通过Feign请求的外部接口都会被监控
 *
 * @version V1.0
 * @author: maoliang
 * @date: 2020/3/27
 */
@Slf4j
public class FeignLogger extends feign.Logger {
    static ThreadLocal<Map<String, String>> logContext = new ThreadLocal();
    static String PATH = "path";
    static String METHOD = "method";
    static String REQUEST_BODY = "body";
    @Override
    protected void logRequest(String configKey, Level logLevel, Request request) {
        Map<String, String> logMap = new HashMap<>(3);
        logMap.put(PATH, request.url());
        logMap.put(METHOD, request.method());
        logMap.put(REQUEST_BODY, request.body() == null ? null :
                request.charset() == null ? null : new String(request.body(), request.charset()));
        logContext.set(logMap);
    }
    @Override
    protected Response logAndRebufferResponse(
            String configKey, Level logLevel, Response response, long elapsedTime) throws IOException {
        Map<String, String> requetParam = logContext.get();
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder
                .append(requetParam.get(METHOD)).append(" ")
                .append(response.status()).append(" ")
                .append(requetParam.get(PATH)).append(" ")
                .append(elapsedTime);
        if (requetParam.get(REQUEST_BODY) != null) {
            stringBuilder.append(" REQUEST BODY:").append(requetParam.get(REQUEST_BODY));
        }
        logContext.remove();
        // 返回参数
        if (response.body() != null && !(response.status() == 204 || response.status() == 205)) {
            byte[] bodyData = Util.toByteArray(response.body().asInputStream());
            if (bodyData.length > 0) {
                String responseBody = decodeOrDefault(bodyData, UTF_8, "Binary data");
                stringBuilder
                        .append(" RESPONSE BODY:")
                        .append(responseBody.replaceAll("\\s*|\t|\r|\n", ""));
            }
            log.info(stringBuilder.toString());
            return response.toBuilder().body(bodyData).build();
        }
        log.info(stringBuilder.toString());
        return response;
    }
    protected IOException logIOException(String configKey, Level logLevel, IOException ioe, long elapsedTime) {
        Map<String, String> requetParam = logContext.get();
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder
                .append(requetParam.get(METHOD)).append(" ")
                .append(ioe.getClass().getSimpleName()).append(" ")
                .append(requetParam.get(PATH)).append(" ")
                .append(elapsedTime);
        if (requetParam.get(REQUEST_BODY) != null) {
            stringBuilder.append(" REQUEST BODY:").append(requetParam.get(REQUEST_BODY));
        }
        log.warn(stringBuilder.toString());
        logContext.remove();
        return ioe;
    }
    @Override
    protected void log(String configKey, String format, Object... args) {
        if (log.isInfoEnabled()) {
            log.info(String.format(methodTag(configKey) + format, args));
        }
    }
}

 

step 2.注册FeignLogger

-> 注册FeignLogger, 设置 Logger.Level为FULL

import feign.Logger;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
/**
 * 注册FeignClient请求日志配置
 *
 * @version V1.0
 * @author: mlxnle
 * @date: 2020/04/01
 */  
@Configuration  
public class FeignLoggerConfig {  
    @Bean  
    Logger.Level loggerLevel() {  
        return Logger.Level.FULL;  
    }  
    @Bean  
    Logger feignLogger() {  
        return new FeignLogger(); // 你的继承类 
    }  
}

 

step 3.开始你的请求

-> 没有第三步! 惊不惊喜

  • 大小: 7.4 KB
分享到:
评论

相关推荐

    Feign HTTP Client 使用指南 - Tower.pdf

    默认情况下,feign通过jdk中的HttpURLConnection向下游服务发起http请求,这种情况下,由于缺乏连接池的支持,在达到一定流量的后服务肯定会出问题.本指南详细介绍feign的实现以及优化

    Feign HTTP Client 使用指南 - Tower.zip

    Feign HTTP Client 使用指南 - Tower.zip Feign HTTP Client 使用指南 - Tower.zip

    fantj2016#java-reader#8. SpringCloud-Feign覆盖默认配置及日志打印1

    但是,如果是,则请注意将其从任何@ComponentScan中排除,否则将包含此配置,因为它将成为feign.Decoder,feign.Encoder,fei

    [享学Feign] 九、Feign + OkHttp和Feign + Apache HttpClient哪个更香?

    前八篇文章介绍完了feign-core核心内容,从本篇开始将介绍它的“其它模块”。其实核心模块可以独立的work,但是不免它的能力偏弱,比如只能编码字符串类型、只能解码字符串类型,默认使用java.net.HttpURLConnection...

    Feign调用401.zip

    我们在使用spring cloud时如果集成了springsecurity,那么应用服务A再调用服务B时使用Feign请求会出现401授权认证的问题,那么解决办法就是在feign调用请求时把token携带过去就可以解决这个问题了,引入资源包中的...

    springcloud应用之feign

    springcloud应用之feign

    feign-reactive:受https:github.comOpenFeign项目启发的React式Feign客户端

    假装React 将Feign与Spring WebFlux一起使用 ... feign-reactor-rx2 :React堆Feign的Rx2兼容实现(取决于feign-reactor-webclient) feign-reactor-jetty :基于React堆Feign的实验性Reactive Jetty客户端实现(不依

    feign+springboot的使用

    feign+springboot的使用

    手写RPC框架Feign

    了解RPC框架Feign并实现简单RPC框架

    eureka集群和feign结合.zip

    eurekaserver-a,eurekaserver-b,eurekaclient-a,eurekaclient-b是做eureka-server和eureka-client以及整合feign的,该demo整合了通过eureka来创建基于spring cloud的服务注册中心,以及通过feign来进行微服务接口调用.

    feign-gson-9.5.1

    feign-gson-9.5.1feign-gson-9.5.1 feign-gson-9.5.1feign-gson-9.5.1feign-gson-9.5.1

    从零开始学springcloud-Feign附带的示例代码

    从零开始学springcloud-Feign附带的示例代码,改代码是feign调用client的示例

    feign-form-spring-3.8.0-API文档-中文版.zip

    赠送jar包:feign-form-spring-3.8.0.jar; 赠送原API文档:feign-form-spring-3.8.0-javadoc.jar; 赠送源代码:feign-form-spring-3.8.0-sources.jar; 赠送Maven依赖信息文件:feign-form-spring-3.8.0.pom; ...

    feign以XML格式传输-技术点eureka-feign-jackson.zip

    知识点:feign以XML格式传输,domain通过Jackson转成XML, 项目总体理解:此文档搭建了eureka注册中心,和生产者,以及feign消费者。feign消费者以XML格式传输数据

    feign-parent.rar

    微服务(fegn,eurake)的很精简的demo,分以下几个模块:很合适初学者,以及阅读源码 eureka-client eureka-feign-client eureka-server

    微服务springcloud之feign使用demo

    Feign是一个声明式的Web Service客户端,它能够让Web Service客户端的编写变得更加容易(你只需创建一个接口,并在接口上添加相应注解即可)。除了Feign自带的注解外它还支持JAX-RS注解,SpringCloud又为Feign增加了...

    feign-core-10.12-API文档-中文版.zip

    赠送jar包:feign-core-10.12.jar; 赠送原API文档:feign-core-10.12-javadoc.jar; 赠送源代码:feign-core-10.12-sources.jar; 赠送Maven依赖信息文件:feign-core-10.12.pom; 包含翻译后的API文档:feign-core...

    feign-consumer例子

    feign例子,创建一个接口并用注解的方式来配置它,即可完成对服务提供方的接口绑定。

    FeignConfig.java

    Spring Cloud Feign的Decoder 在HTTP协议不是很规范的情况下,需要配置Decoder 具体来说:就是返回数据是JSON,而ContentType 为 text/html;charset=UTF-8

    feign-form-3.8.0-API文档-中文版.zip

    赠送jar包:feign-form-3.8.0.jar; 赠送原API文档:feign-form-3.8.0-javadoc.jar; 赠送源代码:feign-form-3.8.0-sources.jar; 赠送Maven依赖信息文件:feign-form-3.8.0.pom; 包含翻译后的API文档:feign-form...

Global site tag (gtag.js) - Google Analytics