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

Kita ·
更新时间:2024-09-20
· 842 次阅读

问题:在微服务中如何对请求日志统一输出?

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

日志组件如下:

工具类

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, "M2"), M3(40, 60, "M3"), M4(60, 80, "M4"), M5(80, 100, "M5"), M6(100, 150, "M6"), M7(150, 200, "M7"), M8(200, 300, "M8"), M9(300, 999999999, "M9"), ; 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文件如下

com.jhjcn jhjcn-common-logger jar 日志组件 4.0.1 3.9 10.2.3 2.1.2.RELEASE com.alibaba fastjson 1.2.61 javax.servlet javax.servlet-api ${javax.servlet-api.version} org.apache.commons commons-lang3 ${commons-lang3.version} io.github.openfeign feign-core 10.2.3 compile org.springframework.boot spring-boot-starter-log4j2 2.1.7.RELEASE org.apache.logging.log4j log4j-slf4j-impl

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 TRACE_ID_THREADLOCAL = new ThreadLocal(); private final static ThreadLocal SPAN_ID_THREADLOCAL = new ThreadLocal(); private final static ThreadLocal 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文件中引用日志组件

com.xxxx xxxx-common-logger 1.0-SNAPSHOT

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

 

简书原创地址:https://www.jianshu.com/p/198555eaf223 


作者:hong_myth



微服务

需要 登录 后方可回复, 如果你还没有账号请 注册新账号