背景

  我们项目中现有日志系统,采用的是slf4j+logback这套日志组件,也是Java生态里面比较常用的一个日志组件,但是随着分布式的演进,这套组件明显存在以下几个问题:

  1.各种无关日志穿行其中,导致我们可能无法直接定位整个操作流程。因此,我们可能需要对一个用户的操作流程进行归类标记,既在其日志信息上添加一个唯一标识,比如使用线程+时间戳,或者用户身份标识等;从大量日志信息中grep出某个用户的操作流程。
  2.无法做信息埋点,也就不方便做后续系统、业务上进行分析

  3.日志排查不方便,需要通过linux命令去导出或者在线查看日志

解决方案

   笔者之前在携程集团的时候,内部已经孵化了大量的中间件,其中分布式日志组件已经应用在各大事业部下的不同应用,据统计整个集团上万个应用都接入到这个日志组件,根据印象大概画了一个设计图

正文

  本篇博客主题是MDC(MDC 全称是 Mapped Diagnostic Context,可以粗略的理解成是一个线程安全的存放诊断日志的容器),其具体流程是通过某些标识将整个轨迹串起来,例如A-B-C-远程接口-D这条链路相关日志信息在日志文件里可以通过某个标识快速查找。下面介绍下目前我负责的项目中日志方案

logback.xml

  将traceId配置在logback.xml,有点像占位符的方式

MDC

将对应的traceId变量通过MDC写入

源码分析

   1.MDC是什么?

    下图可知MDC是slf4j-api的一个类,里面提供了put,get,remove等方法,看完源码其实可知就是一个ThreadLocal,每put一个元素就放到里面,当调用logger.info的时候将ThreadLocal变量取出赋到输出日志

    

由上可知

1 MDCAdapter 是一个适配接口,存放于spi包下面,由此便知MDCAdapter是为了适配其它日志组件

2 MDC 提供的 put 方法,可以将一个 K-V 的键值对放到容器中,并且能保证同一个线程内,Key 是唯一的,不同的线程 MDC 的值互不影响

3 在 logback.xml 中,在 layout 中可以通过声明 %X{REQ_ID} 来输出 MDC 中 REQ_ID 的信息

4 MDC 提供的 remove 方法,可以清除 MDC 中指定 key 对应的键值对信息

LogbackMDCAdapters源码


  如上是MDC的使用方法以及源码分析,下面介绍的是本地调用外部系统的时候,假设用 的是restTemplate,那么得考虑如何把调用前后的日志情况进行抽取封装,做到统一打印,因为笔者之前的代码是没有做抽取,导致每个不同的调用方法都要手动去写log.info,这样的做法虽然没有大问题,但是明显是比较多余且可以进行抽取

外部接口日志轨迹输出

    调用过程中涉及到外部接口,由于外部接口是在第三方系统,我们无法将traceId传递下去,需要改造我们这边的远程调用代码,由于笔者项目用的是restTemplate,所以需要对restTemplate添加拦截器,用于发送请求前和请求后打印出相关日志,如下是我这边的restTemplate对应的日志拦截器

class MyRequestInterceptor implements ClientHttpRequestInterceptor {

        @Override
public ClientHttpResponse intercept(HttpRequest request, byte[] bytes, ClientHttpRequestExecution execution) throws IOException {
traceRequest(request, bytes); ClientHttpResponse response = execution.execute(request, bytes);
ClientHttpResponse responseCopy = new BufferingClientHttpResponseWrapper(response);
traceResponse(responseCopy);
return responseCopy;
} /**
* 打印请求数据
*
* @param request 请求
* @param bytes 请求体
*/
private void traceRequest(HttpRequest request, byte[] bytes) {
String body = new String(bytes, StandardCharsets.UTF_8);
log.info("Request Body = {}", body);
} /**
* 打印响应结果
*
* @param response 响应结果
* @throws IOException io
*/
private void traceResponse(ClientHttpResponse response) throws IOException {
StringBuilder inputStringBuilder = new StringBuilder();
try (BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(response.getBody(), StandardCharsets.UTF_8))) {
String line = bufferedReader.readLine();
while (line != null) {
inputStringBuilder.append(line);
// inputStringBuilder.append('\n');
line = bufferedReader.readLine();
}
}
log.info("Response Body: {}", inputStringBuilder.toString());
} final class BufferingClientHttpResponseWrapper implements ClientHttpResponse {
private final ClientHttpResponse response;
private byte[] body; BufferingClientHttpResponseWrapper(ClientHttpResponse response) {
this.response = response;
} @Override
public HttpStatus getStatusCode() throws IOException {
return this.response.getStatusCode();
} @Override
public int getRawStatusCode() throws IOException {
return this.response.getRawStatusCode();
} @Override
public String getStatusText() throws IOException {
return this.response.getStatusText();
} @Override
public HttpHeaders getHeaders() {
return this.response.getHeaders();
} @Override
public InputStream getBody() throws IOException {
if (this.body == null) {
this.body = StreamUtils.copyToByteArray(this.response.getBody());
}
return new ByteArrayInputStream(this.body);
} @Override
public void close() {
this.response.close();
} }
}

  

最后

  以上就是关于MDC常见的使用场景,包括携程里面的日志组件其实内部也是通过MDC实现,只不过是根据业务做了调整,一般分布式环境下最好将日志输出到Redis或者ES,然后提供一个界面查询日志,目前也有很多类似的开源框架集成了分布式链路日志打印+看板

最新文章

  1. ROC & AUC笔记
  2. 还是不想改报告,伊阿忆啊哟-Linux基础继续
  3. DIOCP之DEMO-登陆验证设计(二)
  4. jsp 格式化变量
  5. CCSprite的使用方法大全
  6. Pascal's Triangle
  7. 使用cookie保存页面登录信息
  8. java基础之 创建对象的几种方式
  9. MinGW-64 安装
  10. [string]Reverse Words in a String
  11. GridView 编辑,更新,删除 等操作~~
  12. iOS推送的众多坑
  13. 细说OC中的load和initialize方法
  14. 【LeetCode】138. Copy List with Random Pointer
  15. golang的并发不等于并行
  16. VMware安装Redhat6.5
  17. 进击的新版NavMesh系统:看我飞檐走壁
  18. Hibernate与 MyBatis的比较(转)
  19. 联动加入redmine的wik
  20. Unity3D学习(七):Unity多重采样抗锯齿设置无效的解决办法

热门文章

  1. [源码解析]PyTorch如何实现前向传播(1) --- 基础类(上)
  2. python的函数参数传递方式
  3. Hive架构及搭建方式
  4. 改善深层神经网络-week1编程题(GradientChecking)
  5. Hadoop集群的配置(一)
  6. [no_code][Beta]项目展示博客
  7. 《基于SIR的路边违停行为传播模型研究》
  8. Python课程笔记(九)
  9. 攻防世界 杂项13.can_has_stdio?
  10. 利用Nginx搭建Ambari本地安装源