失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > MDC实现日志跟踪

MDC实现日志跟踪

时间:2023-01-06 08:52:35

相关推荐

MDC实现日志跟踪

使用MDC标注日志上下文

背景

最近在研究ELK,想通过ELK来统一管理日志,并简单分析系统的一些功能,比如:机构下的交易量,交易成功/失败的比例,单位时间内某种交易的笔数,访问系统前50IP……,但是苦于无法建立统一的分析标准,无法实施,想法是把一些业务参数打印到日志中,进行分析统计。

简介

MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。某些应用程序采用多线程的方式来处理多个用户的请求。在一个用户的使用过程中,可能有多个不同的线程来进行处理。典型的例子是 Web 应用服务器。当用户访问某个页面时,应用服务器可能会创建一个新的线程来处理该请求,也可能从线程池中复用已有的线程。在一个用户的会话存续期间,可能有多个线程处理过该用户的请求。这使得比较难以区分不同用户所对应的日志。当需要追踪某个用户在系统中的相关日志记录时,就会变得很麻烦。

一种解决的办法是采用自定义的日志格式,把用户的信息采用某种方式编码在日志记录中。这种方式的问题在于要求在每个使用日志记录器的类中,都可以访问到用户相关的信息。这样才可能在记录日志时使用。这样的条件通常是比较难以满足的。MDC 的作用是解决这个问题。

MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。

好处

如果你的系统已经上线,突然有一天老板说我们增加一些用户数据到日志里分析一下。如果没有MDC我猜此时此刻你应该处于雪崩状态。MDC恰到好处的让你能够实现在日志上突如其来的一些需求如果你是个代码洁癖,封装了公司LOG的操作,并且将处理线程跟踪日志号也封装了进去,但只有使用了你封装日志工具的部分才能打印跟踪日志号,其他部分(比如hibernate、mybatis、httpclient等等)日志都不会体现跟踪号。当然我们可以通过linux命令来绕过这些困扰。使代码简洁、日志风格统一

使用

spirng: import org.slf4j.MDC;springboot: import org.apache.log4j.MDC;

实现

过滤器(Filter)

遇到问题:如何在过滤器中读取controller的返回值(因为直接Response没有提供直接拿到返回值的方法。所以要通过代理来取得返回值)

@WebFilter(urlPatterns = "/*")@Order(1)public class MyFilter implements Filter {private static Logger logger = LoggerFactory.getLogger(MyFilter.class);private static ObjectMapper mapper = new ObjectMapper();@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@SuppressWarnings("unchecked")@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest) request;String body = IOUtils.toString(request.getInputStream());Map<String, Object> reqMap = mapper.readValue(body, Map.class);// 可以在此处解析请求报文,然后将业务参数放入日志打印MDC.put("service_name", "my_service1");MDC.put("trcode", "123456");MDC.put("chid", "999999");ResponseWrapper wrapperResponse = new ResponseWrapper((HttpServletResponse) response);// 转换成代理类ParameterRequestWrapper wrapRequest = new ParameterRequestWrapper(req, reqMap);chain.doFilter(wrapRequest, wrapperResponse);byte[] content = wrapperResponse.getContent();// 获取返回值String str = new String(content, "UTF-8");Map<String, Object> rspMap = JSONUtil.jsonToObj(str, Map.class);Map<String, String> rspHeadMap = (Map<String, String>) rspMap.get("rspHead");MDC.put("rspcode", rspHeadMap.get("rspcode"));MDC.clear();// 清除数据}/** (non-Javadoc)* * @see javax.servlet.Filter#destroy()*/@Overridepublic void destroy() {}}

/*** 返回值输出代理类*/public class ResponseWrapper extends HttpServletResponseWrapper {private ByteArrayOutputStream buffer;private ServletOutputStream out;public ResponseWrapper(HttpServletResponse httpServletResponse) {super(httpServletResponse);buffer = new ByteArrayOutputStream();out = new WrapperOutputStream(buffer);}@Overridepublic ServletOutputStream getOutputStream() throws IOException {return out;}@Overridepublic void flushBuffer() throws IOException {if (out != null) {out.flush();}}public byte[] getContent() throws IOException {flushBuffer();return buffer.toByteArray();}class WrapperOutputStream extends ServletOutputStream {private ByteArrayOutputStream bos;public WrapperOutputStream(ByteArrayOutputStream bos) {this.bos = bos;}@Overridepublic void write(int b) throws IOException {bos.write(b);}@Overridepublic boolean isReady() {return false;}@Overridepublic void setWriteListener(WriteListener arg0) {}}}

拦截器(暂时未实现获取controller中的返回值)

/*** 拦截器* * @author Fan.W* @since 1.8*/public class MDCInterceptor implements HandlerInterceptor {private static final Logger logger = LoggerFactory.getLogger(MDCInterceptor.class);@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {MDC.put("user_name", "fan wei");MDC.put("user_id", "123456");return true;}@Overridepublic void postHandle(HttpServletRequest request,HttpServletResponse response,Object handler,ModelAndView modelAndView) throws Exception {}/***这个方法的主要作用是用于清理资源的,当然这个方法也只能在当前这个Interceptor的preHandle方法的返回值为true时才会执行。*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {MDC.clear();// 清除}}

约束及注册

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="/schema/beans"xmlns:xsi="/2001/XMLSchema-instance" xmlns:tx="/schema/tx"xmlns:mvc="/schema/mvc" xmlns:context="/schema/context"xmlns:aop="/schema/aop"xsi:schemaLocation="/schema/beans /schema/beans/spring-beans.xsd /schema/tx /schema/tx/spring-tx.xsd /schema/mvc/schema/mvc/spring-mvc.xsd/schema/context /schema/context/spring-context.xsd/schema/aop/schema/aop/spring-aop.xsd"default-lazy-init="false"><!--拦截器 --> <mvc:interceptors> <bean class="com.seeker.interceptor.MDCInterceptor"/> </mvc:interceptors> </beans>

spring aop实现(建议)

异常通知要先于controller中ExceptionHandler捕获异常!!!!!

@Aspect@Componentpublic class MyInterceptor {private static Logger logger = LoggerFactory.getLogger(MyInterceptor.class);/*** 拦截类的入口--拦截所有controller类*/@Pointcut("execution(public * com.seeker.controller..*.*(..)) ")public void pointCut() {}/*** 方法调用之前调用* * @param joinPoint*/@Before(value = "pointCut()")public void doBefore(JoinPoint joinPoint) {}/*** 环绕通知* * @param pjp* @return* @throws Throwable*/@SuppressWarnings({ "rawtypes", "unchecked" })@Around("pointCut()")public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {String className = pjp.getTarget().getClass().getName(); // 拦截类String methodName = pjp.getSignature().getName() + "()";Object[] args = pjp.getArgs();// 获取请求参数,可以校验属性// 解析请求报文,使用MDC,打印日志,也可在前置通知中获取MDC.put("service_name", "my_service1");MDC.put("trcode", "123456");MDC.put("chid", "999999");// 此处返回的是拦截的方法的返回值,如果不执行此方法,则不会执行被拦截的方法,利用环绕通知可以很好的做权限管理Object obj = pjp.proceed();MDC.put("rspcode", "0000");return obj;}/*** 异常通知:pjp.proceed();跑出异常即捕获,先于@ExceptionHandler中捕获到* * @param joinPoint* @param e*/@AfterThrowing(pointcut = "pointCut()", throwing = "e")public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {if (e instanceof BizException) {MDC.put("rspcode", ((BizException) e).messageCode);} else {MDC.put("rspcode", "9999");}}}

约束及开启aop

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="/schema/beans"xmlns:xsi="/2001/XMLSchema-instance"xmlns:mvc="/schema/mvc"xmlns:aop="/schema/aop" xmlns:context="/schema/context"xsi:schemaLocation="/schema/beans /schema/beans/spring-beans.xsd/schema/mvc /schema/mvc/spring-mvc.xsd /schema/context /schema/context/spring-context.xsd/schema/aop /schema/aop/spring-aop.xsd"><!--通知spring使用cglib而不是jdk的来生成代理方法 AOP可以拦截到Controller --><aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy></beans>

logback.xml配置

<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>[%X{service_name}] %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] [%X{chid}] [%X{trcode}] [%X{rspcode}] - %m%n</pattern> </encoder>

如果觉得《MDC实现日志跟踪》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。