【原】MDC日志链路设计
背景
我们项目中现有日志系统,采用的是slf4j+logback这套日志组件,也是Java生态里面比较常用的一个日志组件,但是随着分布式的演进,这套组件明显存在以下几个问题:
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日志链路设计的更多相关文章
- C#日志记录设计与实现(BenXHLog)
C#日志记录设计与实现 日志记录: 日志记录在程序设计开发过程中,是非常重要的,可以供调试和记录数据,虽然说有开源的强大日志管理系统,比如apache的Log4Net,功能可谓强悍,但是有时候,不需要 ...
- ERP设计之系统基础管理(BS)-日志模块设计(转载)
原文地址:8.ERP设计之系统基础管理(BS)-日志模块设计作者:ShareERP 日志模块基本要素包括: 用户会话.登录.注销.模块加载/卸载.数据操作(增/删/改/审/弃/关等等).数据恢复.日志 ...
- SpringBoot之微服务日志链路追踪
SpringBoot之微服务日志链路追踪 简介 在微服务里,业务出现问题或者程序出的任何问题,都少不了查看日志,一般我们使用 ELK 相关的日志收集工具,服务多的情况下,业务问题也是有些难以排查,只能 ...
- dubbo traceId透传实现日志链路追踪(基于Filter和RpcContext实现)
一.要解决什么问题: 使用elk的过程中发现如下问题: 1.无法准确定位一个请求经过了哪些服务 2.多个请求线程的日志交替打印,不利于查看按时间顺序查看一个请求的日志. 二.期望效果 能够查看一个请求 ...
- TinyFrame升级之六:全局日志的设计及实现
日志记录显然是框架设计中不可或缺的元素,在本框架中,我们将使用log4net作为日志记录的主体.下面来具体说明如何让框架继承log4net,并通过Autofac进行IOC注入. 首先,定义好我们的Lo ...
- 【Go】类似csv的数据日志组件设计
原文链接:https://blog.thinkeridea.com/201907/go/csv_like_data_logs.html 我们业务每天需要记录大量的日志数据,且这些数据十分重要,它们是公 ...
- 基于 raft 协议的 RocketMQ DLedger 多副本日志复制设计原理
目录 1.RocketMQ DLedger 多副本日志复制流程图 1.1 RocketMQ DLedger 日志转发(append) 请求流程图 1.2 RocketMQ DLedger 日志仲裁流程 ...
- 一只简单的网络爬虫(基于linux C/C++)————读取命令行参数及日志宏设计
linux上面的程序刚开始启动的时候一般会从命令行获取某些参数,比如以守护进程运行啊什么的,典型的例子就是linux下的man,如下图所示 实现该功能可以使用getopt函数实现,该函数在头文件uni ...
- 我们NetCore下日志存储设计
日志的分类 首先往大的来说,日志分2种 ①业务日志: 即业务系统需要查看的日志, 常见的比如谁什么时候修改了什么. ②参数日志: 一般是开发人员遇到问题的时候定位用的, 一般不需要再业务系统里展示. ...
随机推荐
- selenium去特征
code from selenium import webdriver chrome_options = webdriver.ChromeOptions() chrome_options.add_ex ...
- ❤️这应该是Postman最详细的中文使用教程了❤️(新手使用,简单明了)
️这应该是Postman最详细的中文使用教程了️(新手使用,简单明了) 在前后端分离开发时,后端工作人员完成系统接口开发后,需要与前端人员对接,测试调试接口,验证接口的正确性可用性.而这要求前端开发进 ...
- verilog specify
specify block用来描述从源点(source:input/inout port)到终点(destination:output/inout port)的路径延时(path delay),由sp ...
- JVM:类加载与字节码技术-1
JVM:类加载与字节码技术-1 说明:这是看了 bilibili 上 黑马程序员 的课程 JVM完整教程 后做的笔记 内容 类文件结构 字节码指令 下面的内容在后续笔记中: 编译期处理 类加载阶段 类 ...
- docker multi-stage 多阶段构建
多阶段构建 一.需求 二.普通构建 1.编写Dockerfile 2.构建镜像 三.多阶段(multi-stage)构建 1.编写Dockerfile 2.构建镜像 四.比较2个镜像的体积大小 我们在 ...
- CSP-S2021 退役记
首先大家一起恭喜博主以5pts之差与省三擦肩而过!(nmd爷去年都省三今年成功打铁了) 果然这个菜鸡一年不如一年了 upd:T3死在多测上了,随便一个40+28的人可以吊打我 Day -2: 模拟赛, ...
- Java:final,finally 和 finalize 的区别
在Java中,final,final和finalize之间有许多差异.final,final和finalize之间的差异列表如下: No final finally finalize 1 final用 ...
- 记录 mysql 使用时遇到的问题
1,linux平台上mysqld和mysql的区别. 首先,mysql数据库是标准的c/s架构,yum安装时注意到了,有mysql和mysql-server包 mysql是客户端的工具,mysqld ...
- Centos7下安装BlockScout
简介 BlockScout是一个Elixir应用程序,允许用户搜索以太坊网络(包括所有叉子和侧链)上的交易,查看账户和余额以及验证智能合约.BlockScout为用户提供了一个全面,易于使用的界面,以 ...
- iNeuOS工业互联网操作系统,发布实时存储方式:实时存储、变化存储、定时存储,增加设备振动状态和电能状态监测驱动,v3.6.2
目 录 1. 概述... 1 2. 平台演示... 2 3. 存储方式... 2 4. 设备状态和用电状态监控驱动... 3 1. 概述 本次升 ...