背景

  我们项目中现有日志系统,采用的是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,然后提供一个界面查询日志,目前也有很多类似的开源框架集成了分布式链路日志打印+看板

【原】MDC日志链路设计的更多相关文章

  1. C#日志记录设计与实现(BenXHLog)

    C#日志记录设计与实现 日志记录: 日志记录在程序设计开发过程中,是非常重要的,可以供调试和记录数据,虽然说有开源的强大日志管理系统,比如apache的Log4Net,功能可谓强悍,但是有时候,不需要 ...

  2. ERP设计之系统基础管理(BS)-日志模块设计(转载)

    原文地址:8.ERP设计之系统基础管理(BS)-日志模块设计作者:ShareERP 日志模块基本要素包括: 用户会话.登录.注销.模块加载/卸载.数据操作(增/删/改/审/弃/关等等).数据恢复.日志 ...

  3. SpringBoot之微服务日志链路追踪

    SpringBoot之微服务日志链路追踪 简介 在微服务里,业务出现问题或者程序出的任何问题,都少不了查看日志,一般我们使用 ELK 相关的日志收集工具,服务多的情况下,业务问题也是有些难以排查,只能 ...

  4. dubbo traceId透传实现日志链路追踪(基于Filter和RpcContext实现)

    一.要解决什么问题: 使用elk的过程中发现如下问题: 1.无法准确定位一个请求经过了哪些服务 2.多个请求线程的日志交替打印,不利于查看按时间顺序查看一个请求的日志. 二.期望效果 能够查看一个请求 ...

  5. TinyFrame升级之六:全局日志的设计及实现

    日志记录显然是框架设计中不可或缺的元素,在本框架中,我们将使用log4net作为日志记录的主体.下面来具体说明如何让框架继承log4net,并通过Autofac进行IOC注入. 首先,定义好我们的Lo ...

  6. 【Go】类似csv的数据日志组件设计

    原文链接:https://blog.thinkeridea.com/201907/go/csv_like_data_logs.html 我们业务每天需要记录大量的日志数据,且这些数据十分重要,它们是公 ...

  7. 基于 raft 协议的 RocketMQ DLedger 多副本日志复制设计原理

    目录 1.RocketMQ DLedger 多副本日志复制流程图 1.1 RocketMQ DLedger 日志转发(append) 请求流程图 1.2 RocketMQ DLedger 日志仲裁流程 ...

  8. 一只简单的网络爬虫(基于linux C/C++)————读取命令行参数及日志宏设计

    linux上面的程序刚开始启动的时候一般会从命令行获取某些参数,比如以守护进程运行啊什么的,典型的例子就是linux下的man,如下图所示 实现该功能可以使用getopt函数实现,该函数在头文件uni ...

  9. 我们NetCore下日志存储设计

    日志的分类 首先往大的来说,日志分2种 ①业务日志: 即业务系统需要查看的日志, 常见的比如谁什么时候修改了什么. ②参数日志: 一般是开发人员遇到问题的时候定位用的, 一般不需要再业务系统里展示. ...

随机推荐

  1. 关于 我的博客和Git-hub

    欢迎大家到我的GitHub 热烈讨论 https://github.com/ljj-19951010 由于另一个博客忘了怎么登陆了,换用此博客(仅供个人学习使用,请勿传播) 如果想看 特别详细的教程请 ...

  2. 从原理—实战分析SQL注入

    前言 SQL注入是web安全中最常见的攻击方式,SQL注入有很多方法,但如果只知道payload或只用用sqlmap,不知道原理,感觉也很难掌握,这次就总结一下我所遇到的SQL注入方法,原理分析+题目 ...

  3. 易维巡APP技术支持

    亲爱的用户 如果您在使用我们的产品时遇到任何问题,请随时与我们联系,我们将全力全意为您解决! 请发邮件与我们联系,我们将24小时为您服务! 电话:18251927768 邮箱地址:xshm999@16 ...

  4. Vue3+Typescript+Node.js实现微信端公众号H5支付(JSAPI v3)教程--各种填坑

    ----微信支付文档,不得不说,挺乱!(吐槽截止) 功能背景 微信公众号中,点击菜单或者扫码,打开公众号中的H5页面,进行支付. 一.技术栈 前端:Vue:3.0.0,typescript:3.9.3 ...

  5. Stack2 攻防世界题目分析

    ---XCTF 4th-QCTF-2018 前言,怎么说呢,这题目还是把我折磨的可以的,我一开始是没有看到后面的直接狙击的,只能说呢. 我的不经意间的粗心,破坏了你许多的温柔 1.气的我直接检查保护: ...

  6. 2021.10.27考试总结[冲刺NOIP模拟17]

    T1 宝藏 发现每个数成为中位数的长度是关于权值单调的.线段树二分判断是否合法,单调指针扫即可. 考场上写了二分,平添\(\log\). \(code:\) T1 #include<bits/s ...

  7. NB-IoT的DRX、eDRX、PSM三个模式怎么用?通俗解释,看完就懂!

    面我们讲了不少NB-IOT的应用.软件和硬件设计的变动. (链接在文章末尾). 今天讲讲NB-IoT的三大模式,在各种物联网和智能硬件场景中的使用方法 DRX.eDRx.PSM是什么? DRX虽然叫做 ...

  8. ab矩阵(实对称矩阵)

    今天在做题时巧遇了很多此类型的矩阵,出于更快解,对此进行学习.(感谢up主线帒杨) 1.认识ab矩阵 形如:主对角线元素都是a,其余元素都是b,我们称之为ab矩阵(默认涉及即为n×n阶) 2.求|A| ...

  9. (转载)gcc -l参数和-L参数

    -l参数就是用来指定程序要链接的库,-l参数紧接着就是库名,那么库名跟真正的库文件名有什么关系呢?就拿数学库来说,他的库名是m,他的库文件名是libm.so,很容易看出,把库文件名的头lib和尾.so ...

  10. Python super(Todo,self).__init__() TypeError: super() argument 1 must be type, not classobj

    示例如下 class A(): def __init__(self):pass class B(A): def __init__(self): super(A, self).__init__() 当调 ...