基于MDC的日志链路监控

gateway链路监控
现状:目前查找一个交易的详情,需要通过portainer进入请求所在的容器查找流水号对应的日志。由于请求很多,同一请求的日志可能不在一起,并且同一请求中有的日志无法添加流水号。
目的:通过流水号对网关日志进行查询,可得到同一请求的所有日志。
原理图:
基于MDC的日志链路监控_第1张图片
方法:
将gateway的同一请求的日志添加流水号:

  • 请求进入时日志添加流水号
  • 响应回来时日志添加流水号,由于请求和响应不是同一个线程处理
  • 发送响应时删除日志流水号
  • 异步操作传递流水号

通过ELK对网关日志进行收集,以gateway-{date}归集一个index,做为基础的服务日志。
Filebeat:换行的日志当作一条发送到kafka
Logstash:解析orderNo当作es的一个字段

查询:
配置后端服务的日志收集地址,在查询交易日志时可通过api查到关联的下游日志
以api-invoke-his做为交易流水库,根据流水号,网关类型和日期到gateway-{date}(昨今明天)查询具体gateway日志,根据apiName查到关联的下游日志index,综合得到整条链路的日志

缺点:

  • 代码侵入性比较强,需要改动四处地方
  • 主动配置的地方较多,需要配置交易的日志收集index,即交易的链路。需要提前知道交易的链路。对比skywalking,引入agent即可打印链路的信息,但是无法打印日志。
  • 如果想监控mysql、es,需要自己开发agent

系统日志监控:
背景:
微服务中系统日志散落在各个机器中,无法集中查看,目前通过elk可收集各个系统的日志在kibana查看。但是日志结构比较松散,出了问题排查困难,需要事先找到机器。此外如果日志量大,难以查看报错日志的上下文。因此需要快速找到一次请求链上的所有日志。因此需要一种快速知道哪里报错以及找到报错上下文日志的方案。

方案简述:
将同一请求/处理逻辑的日志添加相同流水号,方便系统异常时查到关联日志。其中所有服务的异常日志放入同一个es的index,方便直接展示错误日志。如果该日常包含流水号,可去相关服务查询详细日志。如果知道请求经过的服务,可以查询链路的相关日志。错误日志也放入kafka的topic中,便于监控系统对接告警。

目的:

  • 快速找到报错的日志及流水号,便于查看系统启动时是否成功
  • 通过流水号找到统一请求的日志。(需要手动关联同一流水号的多个服务,无法做到api级别的动态关联,如果查找所有index比较耗费性能)

原理图:
基于MDC的日志链路监控_第2张图片
流程:

  • Filebeat监听服务日志,定义logtopic放入kafka的openapi_{logtopic}中
  • Logstash监听openapi_{logtopic}的topic,将数据格式化后输出至三个方向:
  • 全量输出至{logtopic}_{date},即在es中按logtopic和日期为index进行保存
  • 级别为error的日志一方面输出至openapi_error_{date},即以error和日期为index进行保存,另一方面输出至topic为openapi-error的kafka中,供监控告警使用。
  • 这些输出是并行的。
  • Monitor以error_{date}为基础数据源进行查看,如果异常日志包含流水号,则可跳转至{logtopic}-{date}进行查看服务的日志。如果查看相关链路日志,需要联合index进行查询。

需要解决的问题:

  • 流水号在服务内部传递
  • 流水号在服务间传递
  • 流水号在异步线程传递
  • 服务在某个方法中传递

服务日志添加流水号的方式:

  • request filter:web请求到来时,如果此时线程中已经设置过流水号,不用再设置。如果没有设置且header中包含orderNo,则将其作为当前线程的order,否则新生成orderNo并设置。确保filter是请求第一个进入的,避免一些log记录不到流水号。
  • Feign intercepter:服务通过feign调用其他服务时,将当前线程的orderNo放入header中,保证下游服务可以获得orderNo。如果有使用hystrix,需要在hystrix线程池中传递mdc
  • 异步线程池:异步执行时,无法传递流水号,因此需要使用特定的线程池,可以传递流水号
  • 单独执行的方法:redis,mq的监听,执行定时任务,由特定事件执行,而非web请求触发,通过aop在方法执行前设置MDC,完成后清除

改造的地方:

  • 异步执行使用特定线程池
  • 单独执行的方法添加注解
  • Filebeat增加换行的配置,只传输*All的日志
  • Logstash增加orderNo的解析,log_level为error的传入es及kafka中

问题:
如果不是logger.info或logger.error的日志,如果出现异常如NullPointerException,虽然是同一个线程处理,但流水号无法继承。需要有GlobalExceptionAspect捕获全局异常

Async-threadpool:

  • 提供一个可以直接使用的线程池,@Async直接使用。使用方便,不用什么配置
  • 提供获取线程池的静态方法,可以通过方法获取,通过@Bean注入,便于设置线程池参数。场景:有多个参数不同的线程池需求
  • 提供线程池装饰器,自定义的线程池加上装饰器即可添加mdc

Method-aspect:
有些代码的执行并不由web请求触发,而是由定时任务,mq的监听触发,执行某个方法。因此需要对方法进行AOP拦截,执行前添加MDC,执行完后删除。此外注意发生异常时也需要拦截并删除MDC。
异常如果不用logger.error/info记录,则无法打印MDC,自动抛出的异常(如除数为0)虽然在一个线程处理但mdc为空。因此AOP需要拦截异常并用logger.error进行记录。
此外,@PostConstruct执行的方法无法添加MDC。

Feign-interceptor:
如果当前线程包含MDC,则在header中设置,否则不做处理。由于有可能使用到hystrix进行限流熔断,需要对hystrix进行配置

Request-interceptor:
如果当前线程包含MDC,不做处理;如果不包含,查看header是否含有orderNo,如果包含,将orderNo作为流水号设为MDC,否则生成新orderNo作为MDC。

注意的几个点:

  • Filebeat同步的是**All.log日志,避免error出现两次
  • MDC put后再put,会清除
  • Request filter的order; filter在interceptor前,因此需要用filter且优先级最高,保证请求到来第一个进入
  • Gateway无法使用skywalking,因为是nio,请求和响应是不同线程处理,响应无法与请求的mdc保持一致
  • 第三方jar包的类名最好加上服务名称,避免重复

你可能感兴趣的