微服务请求日志统一处理方案

2020-04-03  本文已影响0人  Sina华
问题:在微服务中如何对请求日志统一输出?

新建日志组件,日志组件对请求进行拦截处理,输出请求入参、出参。其他各微服务引用日志组件,对日志统一输出

日志组件如下:
工具类

1、新建TimeCostEnum 请求耗时类,用于对请求处理耗时进行耗时级别定义

package com.jhjcn.common.logger;
/**
 * @Deacription TODO
 * @Author jianhua.hong
 * @Date 2020/4/3 10:39
 **/
public enum TimeCostEnum {
    M1(0, 20, "M1"),
    M2(20, 40, "M1"),
    M3(40, 60, "M1"),
    M4(60, 80, "M1"),
    M5(80, 100, "M1"),
    M6(100, 150, "M1"),
    M7(150, 200, "M1"),
    M8(200, 300, "M1"),
    M9(300, 999999999, "M1"),
    ;
    private int beginValue;
    private int endValue;
    private String costMark;
    TimeCostEnum(int beginValue, int endValue, String costMark) {
        this.beginValue = beginValue;
        this.endValue = endValue;
        this.costMark = costMark;
    }
    public int getBeginValue() {
        return beginValue;
    }
    public int getEndValue() {
        return endValue;
    }
    private String getCostMark() {
        return costMark;
    }
    public static String costMark(long costTime) {
        String mark = "M0";
        for (TimeCostEnum timeCostEnum : TimeCostEnum.values()) {
            long beginValue = timeCostEnum.getBeginValue();
            long endValue = timeCostEnum.getEndValue();
            if (beginValue < costTime && costTime <= endValue) {
                mark = timeCostEnum.getCostMark();
                break;
            }
        }
        return mark;
    }
}

2、新建LogComponentConstant 日志组件常量类

package com.jhjcn.common.logger;


/**
 * @Deacription TODO
 * @Author jianhua.hong
 * @Date 2020/4/3 10:32
 **/
public class LogComponentConstant {

    public static final String TRACE_ID = "traceId";

}
核心组件

1、新搭建xxxx-common-logger 工程,其pom文件如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://maven.apache.org/POM/4.0.0"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

   <groupId>com.jhjcn</groupId>
   <artifactId>jhjcn-common-logger</artifactId>
   <packaging>jar</packaging>
   <description>日志组件</description>

   <properties>
       <javax.servlet-api.version>4.0.1</javax.servlet-api.version>
       <commons-lang3.version>3.9</commons-lang3.version>
       <feign-core.version>10.2.3</feign-core.version>
       <spring-cloud-openfeign-core.version>2.1.2.RELEASE</spring-cloud-openfeign-core.version>
   </properties>
   <dependencies>
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>fastjson</artifactId>
           <version>1.2.61</version>
       </dependency>
       <dependency>
           <groupId>javax.servlet</groupId>
           <artifactId>javax.servlet-api</artifactId>
           <version>${javax.servlet-api.version}</version>
       </dependency>
       <dependency>
           <groupId>org.apache.commons</groupId>
           <artifactId>commons-lang3</artifactId>
           <version>${commons-lang3.version}</version>
       </dependency>
       <dependency>
           <groupId>io.github.openfeign</groupId>
           <artifactId>feign-core</artifactId>
           <version>10.2.3</version>
           <scope>compile</scope>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-log4j2</artifactId>
           <version>2.1.7.RELEASE</version>
           <exclusions>
               <exclusion>
                   <groupId>org.apache.logging.log4j</groupId>
                   <artifactId>log4j-slf4j-impl</artifactId>
               </exclusion>
           </exclusions>
       </dependency>
   </dependencies>
</project>

2、新建MethodLogInfo类,用于封装请求信息

package com.jhjcn.common.logger;
import lombok.Data;
import java.io.Serializable;
/**
 * @Deacription TODO
 * @Author jianhua.hong
 * @Date 2020/4/3 14:31
 **/
@Data
public class MethodLogInfo implements Serializable {
    private String path;
    private String className;
    private String method;
    private String methodName;
    private String paramsStr;
    private boolean multiFileMark;
}

3、新建RequestLogContext 请求日志处理上下文类,用于对请求设置链路ID

package com.jhjcn.common.logger;
/**
 * @Deacription TODO
 * @Author jianhua.hong
 * @Date 2020/4/2 18:29
 **/
public final class RequestLogContext {
    private final static ThreadLocal<String> TRACE_ID_THREADLOCAL = new ThreadLocal<>();
    private final static ThreadLocal<String> SPAN_ID_THREADLOCAL = new ThreadLocal<>();
    private final static ThreadLocal<String> PARENT_SPAN_ID_THREADlOCAL = new ThreadLocal<>();
    public static void addTraceId(String id) {
        TRACE_ID_THREADLOCAL.set(id);
    }
    public static String getTraceId() {
        return TRACE_ID_THREADLOCAL.get();
    }
    public static void removeTraceId() {
        TRACE_ID_THREADLOCAL.remove();
    }
    public static void addSpanId(String id) {
        SPAN_ID_THREADLOCAL.set(id);
    }
    public static String getSpanId() {
        return SPAN_ID_THREADLOCAL.get();
    }
    public static void removeSpanId() {
        SPAN_ID_THREADLOCAL.remove();
    }
    public static void addParentSpanId(String id) {
        PARENT_SPAN_ID_THREADlOCAL.set(id);
    }
    public static String getParentSpanId() {
        return PARENT_SPAN_ID_THREADlOCAL.get();
    }
    public static void removeParentSpanId() {
        PARENT_SPAN_ID_THREADlOCAL.remove();
    }
}

4、新建AbstractLogHandler日志处理抽象类,用于封装日志处理公用方法

package com.jhjcn.common.logger;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
/**
 * @Deacription TODO
 * @Author jianhua.hong
 * @Date 2020/4/2 18:33
 **/
public abstract class AbstractLogHandler {
    protected HttpServletRequest getRequest() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        return request;
    }
    protected void processRequestTraceId() {
        HttpServletRequest request = this.getRequest();
        String traceId = request.getHeader(LogComponentConstant.TRACE_ID);
        if (StringUtils.isBlank(traceId)) {
            traceId = RequestSeqHelper.nextId();
        }
        RequestLogContext.addTraceId(traceId);
    }
    protected boolean isOutPutLog(Object[] params) {
        boolean outputMark = this.isMultilFileMark(params);
        if (outputMark) {
            return false;
        } else {
            return true;
        }
    }
    protected boolean isMultilFileMark(Object[] params) {
        boolean multilFileMark = false;
        for (Object param : params) {
            if (param instanceof MultipartFile) {
                multilFileMark = true;
                break;
            }
        }
        return multilFileMark;
    }
    protected String getTimeCostFlag(long cost) {
        String costFlag = TimeCostEnum.costMark(cost);
        return costFlag;
    }
    protected MethodLogInfo buildMethodInfo(JoinPoint point) {
        HttpServletRequest request = this.getRequest();
        String path = request.getRequestURL().toString();
        String className = point.getSignature().getDeclaringTypeName();
        String method = request.getMethod();
        String methodName = point.getSignature().getName();
        Object[] params = point.getArgs();
        boolean multilFileMark = this.isMultilFileMark(params);
        MethodLogInfo requestLogInfo = new MethodLogInfo();
        requestLogInfo.setPath(path);
        requestLogInfo.setClassName(className);
        requestLogInfo.setMethod(method);
        requestLogInfo.setMethodName(methodName);
        if (!multilFileMark) {
            String paramsStr = JSONObject.toJSONString(params);
            requestLogInfo.setParamsStr(paramsStr);
        } else {
            requestLogInfo.setMultiFileMark(true);
        }
        return requestLogInfo;
    }
}

5、新建RequestLogHandler类,用于处理请求controller层日志输出

package com.jhjcn.common.logger;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import java.text.MessageFormat;
/**
 * @Deacription TODO
 * @Author jianhua.hong
 * @Date 2020/4/2 17:40
 **/
@Slf4j
@Aspect
public class RequestLogHandler extends AbstractLogHandler {
    private static final String REQUEST_MESSAGE_FORMAT = "URL=[{0}],method=[{1}],handle class=[{2}],handle method=[{3}],Param=[{4}],Response=[{5}],cost time=[{6}ms-({7})]";
    private static final String REQUEST_MESSAGE_ERROR_FORMAT = "URL=[{0}],method=[{1}],handle class=[{2}],handle method=[{3}],Param=[{4}]";
    private static final String REQUEST_MESSAGE_MULIT_FORMAT = "URL=[{0}],request method=[{1}],handle class=[{2}],handle method=[{3}]";

    @Pointcut("@within(org.springframework.web.bind.annotation.RestController) && execution(public * *(..) )")
    public void controllerPoincut() {

    }
    @Around("controllerPoincut()")
    public Object doControllerAround(ProceedingJoinPoint point) throws Throwable {
        Long startTime = System.currentTimeMillis();
        super.processRequestTraceId();
        String traceId = RequestLogContext.getTraceId();
        MethodLogInfo methodLogInfo = super.buildMethodInfo(point);
        Object controllerResp = point.proceed();
        Long endTime = System.currentTimeMillis();
        Long cost = endTime - startTime;
        String path = methodLogInfo.getPath();
        String className = methodLogInfo.getClassName();
        String method = methodLogInfo.getMethod();
        String methodName = methodLogInfo.getMethodName();
        if (!methodLogInfo.isMultiFileMark()) {
            String paramsStr = methodLogInfo.getParamsStr();
            String timeFlag = getTimeCostFlag(cost);
            String controllerRespStr = JSONObject.toJSONString(controllerResp);
            Object[] paramsArray = new Object[]{
                    path, method, className, methodName, paramsStr, controllerRespStr, cost, timeFlag
            };
            log.info("[request-log-around-{}]{}", traceId, MessageFormat.format(REQUEST_MESSAGE_FORMAT, paramsArray));
            log.info("[timec-log-{}],class=[{}],method=[{}],cost time=[{}-({})]", traceId, className, methodName, cost, timeFlag);
        } else {
            Object[] paramsArray = new Object[]{
                    path, method, className, methodName
            };
            log.info("[request-log-around-{}]{}", traceId, MessageFormat.format(REQUEST_MESSAGE_MULIT_FORMAT, paramsArray));
        }
        return controllerResp;
    }
    @AfterThrowing(pointcut = "controllerPoincut()", throwing = "e")
    public void handle(JoinPoint point, Exception e) {
        super.processRequestTraceId();
        String traceId = RequestLogContext.getTraceId();
        MethodLogInfo methodLogInfo = super.buildMethodInfo(point);
        String path = methodLogInfo.getPath();
        String className = methodLogInfo.getClassName();
        String method = methodLogInfo.getMethod();
        String methodName = methodLogInfo.getMethodName();
        String paramsStr = methodLogInfo.getParamsStr();
        Object[] paramsArray = new Object[]{
                path, method, className, methodName, paramsStr
        };
        log.info("[request-log-pre-{}]{}", traceId, MessageFormat.format(REQUEST_MESSAGE_ERROR_FORMAT, paramsArray));
        log.info("[request-log-after-{}],handle class=[{}],handle method=[{}]异常", traceId, className, methodName);
    }
}

6、新建ServiceLogHandler 类,用于对@servcie层日志处理,记录入参、出参

package com.jhjcn.common.logger;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import java.text.MessageFormat;
/**
 * @Deacription TODO
 * @Author jianhua.hong
 * @Date 2020/4/2 20:34
 **/
@Slf4j
@Aspect
public class ServiceLogHandler extends AbstractLogHandler {
    private static final String SERVICE_MESSAGE_FORMAT = "handle class=[{0}],handle method=[{1}],Param=[{2}],Response=[{3}],cost time=[{4}ms-({5})]";
    private static final String SERVICE_MESSAGE_ERROR_FORMAT = "handle class=[{0}],handle method=[{1}],Param=[{2}]";
    @Pointcut("@within(org.springframework.stereotype.Service) && execution(public * *(..))")
    public void servicePointcut() {

    }
    @Around("servicePointcut()")
    public Object doControllerAround(ProceedingJoinPoint point) throws Throwable {
        super.processRequestTraceId();
        String traceId = RequestLogContext.getTraceId();
        Long startTime = System.currentTimeMillis();
        String methodName = point.getSignature().getName();
        String className = point.getSignature().getDeclaringTypeName();
        Object[] params = point.getArgs();
        boolean outputLogMark = super.isOutPutLog(params);
        Object serviceResp = point.proceed();
        Long endTime = System.currentTimeMillis();
        Long costTime = endTime - startTime;
        if (outputLogMark) {
            String paramsStr = JSONObject.toJSONString(params, SerializerFeature.IgnoreNonFieldGetter);
            String rspStr = JSONObject.toJSONString(serviceResp, SerializerFeature.IgnoreNonFieldGetter);
            String timeFlag = getTimeCostFlag(costTime);
            Object[] paramsArray = new Object[]{
                    className, methodName, paramsStr, rspStr, costTime, timeFlag
            };
            log.info("[service-log-around-{}]{}", traceId, MessageFormat.format(SERVICE_MESSAGE_FORMAT, paramsArray));
            log.info("[times-log-{}] class=[{}],method=[{}],cost time=[{}-({})]", traceId, className, methodName, costTime, timeFlag);
        }
        return serviceResp;
    }
    @AfterThrowing(pointcut = "servicePointcut()", throwing = "e")
    public void handle(JoinPoint point, Exception e) {
        super.processRequestTraceId();
        String traceId = RequestLogContext.getTraceId();
        String methodName = point.getSignature().getName();
        String className = point.getSignature().getDeclaringTypeName();
        Object[] params = point.getArgs();
        String paramsStr = JSONObject.toJSONString(params, SerializerFeature.IgnoreNonFieldGetter);
        Object[] paramsArray = new Object[]{
                className, methodName, paramsStr
        };
        log.info("[service-log-pre-{}]{}", traceId, MessageFormat.format(SERVICE_MESSAGE_ERROR_FORMAT, paramsArray));
        log.info("[service-log-after-{}],handle class=[{}],handle method=[{}]异常", traceId, className, methodName);

    }
}

7、新建LogHandlerConfig类,用于注入RequestLogHandler 、ServiceLogHandler 请求日志处理组件

package com.jhjcn.common.logger;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * @Deacription TODO
 * @Author jianhua.hong
 * @Date 2020/4/3 9:37
 **/
@Slf4j
@Configuration
public class LogHandlerConfig {
    @Bean
    public RequestLogHandler requestLogHandler() {
        log.info("RequestLogHandler component init");
        return new RequestLogHandler();
    }
    @Bean
    public ServiceLogHandler serviceLogHandler() {
        log.info("ServiceLogHandler component init");
        return new ServiceLogHandler();
    }
}

8、新建FeginRemoteInterceptor类,用于对微服务Fegin调用设置请求链路ID

package com.jhjcn.common.logger;
import feign.RequestInterceptor;
import feign.RequestTemplate;
/**
 * @Deacription TODO
 * @Author jianhua.hong
 * @Date 2020/4/3 10:04
 **/
public class FeginRemoteInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        requestTemplate.header(LogComponentConstant.TRACE_ID, RequestLogContext.getTraceId());
    }
}

9、新建FeginRemoteConfig类,用于注入FeginRemoteInterceptor 拦截器、

package com.jhjcn.common.logger;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * @Deacription TODO
 * @Author jianhua.hong
 * @Date 2020/4/3 10:30
 **/
@Slf4j
@Configuration
public class FeginRemoteConfig {
    @Bean
    FeginRemoteInterceptor feginRemoteInterceptor() {
        log.info("FeginRemoteInterceptor component init");
        return new FeginRemoteInterceptor();
    }
}
组件使用如下

1、微服务工程pom文件中引用日志组件

 <dependency>
            <groupId>com.xxxx</groupId>
            <artifactId>xxxx-common-logger</artifactId>
            <version>1.0-SNAPSHOT</version>
</dependency>

2、微服务启动类加上日志组件扫描路径


企业微信截图_15859019508763.png
上一篇下一篇

猜你喜欢

热点阅读