Java中最简单的添加日志链路的方式之一

Java项目中添加日志链路功能的设计与实现


文章目录

  • Java项目中添加日志链路功能的设计与实现
  • 前言
  • 一、日志链路的概念与作用
  • 二、添加日志链路的设计思路
  • 三、如何支持多线程下的日志打印也附加上日志链路id
    • 1. 示例1:实现Runnable接口,无返回值
    • 2. 示例2:实现Callable接口,有返回值
    • 3.使用示例
  • 总结


前言

在Java项目的开发过程中,日志记录是不可或缺的一部分。它不仅可以帮助我们追踪程序的执行过程,还可以在出现问题时提供关键的调试信息,特别是当线上出现问题时,详细的日志可以帮助我们更快的定位到问题。然而,随着项目的不断壮大和复杂度的增加,单一的日志记录往往难以满足需求。因此,引入日志链路功能,将不同模块、不同层级的日志信息串联起来,形成完整的执行路径,对于提升项目的可维护性和调试效率具有重要意义。

本文主要是针对单体项目,以单项目为基础例子进行展开讲解,。


一、日志链路的概念与作用

日志链路是指将一次完整的业务请求或操作过程中产生的所有日志信息按照时间顺序和逻辑关系串联起来,形成一个完整的日志链。通过日志链路,我们可以清晰地看到业务请求从接收、处理到完成的整个过程,以及其中各个环节的详细执行情况。这对于分析性能瓶颈、定位故障原因以及优化系统性能等方面都具有重要作用。

二、添加日志链路的设计思路

在Java项目中添加日志链路功能,可以从以下几个方面进行设计:

  1. 定义一个全局唯一的标识(如UUID)作为日志链路的ID,用于标识一次完整的业务请求或操作。

简单示例代码:

public class TraceIdGenerator {private static final AtomicLong counter = new AtomicLong(0);/*** 生成一个随机的trace_id。* 这个trace_id由UUID和递增的计数器组成,确保在单一JVM实例中的唯一性。* 如果需要跨多个JVM实例的唯一性,请仅使用UUID部分。* 注意,仅仅使用UUID也并不能保证在极端情况下(例如全球范围内的大量并发请求)的绝对唯一性** @return 生成的trace_id*/public static String generateTraceId() {String uuidPart = UUID.randomUUID().toString().replace("-", ""); // 移除UUID中的横线,使其更简洁long counterPart = counter.incrementAndGet(); // 递增计数器// 拼接UUID和计数器部分,确保在单一JVM实例中的唯一性return uuidPart + "-" + counterPart;}public static void main(String[] args) {// 测试生成几个trace_idfor (int i = 0; i < 5; i++) {System.out.println(generateTraceId());}}
}
  1. 在业务请求的入口处生成该标识,并将其传递给后续的处理环节。此步骤可以统一使用过滤器或者拦截器都可以,正常现在每个项目在进
    入业务流程之前都会有一个解析用户信息的步骤,可以放在同一个步骤上。传递这个标识可以通过自定义日志记录器或使用现有的日志框架(如SLF4J、Logback等)的MDC(Mapped Diagnostic Context)功能来实现。

代码示例:MDC存放的逻辑你可以理解成map类型(底层是ThreadLocal),存放的数据是key、value形式。BaseConstants.TRACE_ID是定义的一个key,TraceIdGenerator.generateTraceId()是生成的链路id,放在MDC之后在线程的上下文就可以直接使用MDC.get(BaseConstants.TRACE_ID)去获取链路id。

public class LogFilter extends OncePerRequestFilter {private final Logger logger = LoggerFactory.getLogger(LogFilter.class);@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {filter(request,response, filterChain);}private void filter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {request.setCharacterEncoding(StandardCharsets.UTF_8.name());try {//将threadId和traceId放入mdc   BaseConstants.TRACE_ID = "TRACE_ID"MDC.put(BaseConstants.TRACE_ID, TraceIdGenerator.generateTraceId());MDC.put(BaseConstants.MDC_THREAD_ID, String.valueOf(Thread.currentThread().getId()));//在响应头中设置traceIdresponse.setHeader(BaseConstants.TRACE_ID,MDC.get(BaseConstants.TRACE_ID));logger.debug("=== begin request processing ===");RequestWrapper requestWrapper = new RequestWrapper(request);chain.doFilter(requestWrapper, response);} finally {logger.debug("=== finish request processing ===");//移除 threadId和traceIdMDC.remove(BaseConstants.TRACE_ID);MDC.remove(BaseConstants.MDC_THREAD_ID);}}
//过滤器需要注册,且需要配置相关的规则
@Configuration
public class WebConfig {@Beanpublic FilterRegistrationBean<LogFilter> platformFilter() {FilterRegistrationBean<LogFilter> filterRegBean = new FilterRegistrationBean<>();//设置过滤器filterRegBean.setFilter(new LogFilter());//设置过滤路径:所有请求路径filterRegBean.addUrlPatterns("/*");//设置过滤顺序:数字越小,会在越前面过滤filterRegBean.setOrder(-9999);return filterRegBean;}
}
  1. 在各个处理环节中,将日志链路的ID作为日志记录的一部分,确保所有与该请求相关的日志都能被正确关联。这里其实就是配置日志文件的输出格式,带上日志链路id即可。可以看到配置的输出中有TRACE_ID,这是因为在第二个步骤中,存放在MDC中的日志链路id的key是TRACE_ID。
logging.file.name=log-demo1.log
logging.level.root=info
logging.level.com.jzs.log=debug
logging.level.com.jzs.log.console=debug
logging.pattern.console=[%d{MM-dd HH:mm:ss.SSS}][%thread][%level][LOG-DEMO1] %cyan([%logger{50}.%F:%L]):[%X{TRACE_ID}] %msg%n
logging.pattern.file=[%d{MM-dd HH:mm:ss.SSS}][%thread][%level][LOG-DEMO1] %cyan([%logger{50}.%F:%L]):[%X{TRACE_ID}] %msg%n
  1. 最后,我们需要一种机制来收集具有相同日志链路ID的日志信息,并按照时间顺序和逻辑关系进行排序和展示。这可以通过日志收集系统(如ELK Stack、ClickHouse+可视化界面等)来实现。这些系统通常支持按照特定字段(如本例中的traceId)对日志进行过滤和排序,从而方便地展示完整的日志链路。

三、如何支持多线程下的日志打印也附加上日志链路id

主要就在于重写类方法上,涉及的是Runnable和Callable这两个接口,下面给出示例和使用方式。

1. 示例1:实现Runnable接口,无返回值

public abstract class MyRunnable implements Runnable {private final Logger log = LoggerFactory.getLogger(MyRunnable.class);private String traceId;public MyRunnable() {traceId = MDC.get(BaseConstants.TRACE_ID);}public MyRunnable(String traceId) {this.traceId = traceId;}@Overridepublic void run() {try {MDC.put(BaseConstants.TRACE_ID, traceId);log.info("MyRunnable::run");doRun();} finally {MDC.remove(BaseConstants.TRACE_ID);}}public abstract void doRun();}

2. 示例2:实现Callable接口,有返回值

public abstract class MyCallable implements Callable {private final Logger log = LoggerFactory.getLogger(MyCallable.class);private String traceId;public MyCallable() {traceId = MDC.get(BaseConstants.TRACE_ID);}public MyCallable(String traceId) {this.traceId = traceId;}@Overridepublic Object call() throws Exception {try {MDC.put(BaseConstants.TRACE_ID, traceId);log.info("TraceCallable::call");return doCall();} finally {MDC.remove(BaseConstants.TRACE_ID);}}public abstract Object doCall();}

3.使用示例

@GetMapping("/test")
public Object test() throws Exception {ExecutorService executorService = Executors.newSingleThreadExecutor();log.info("进入test");new Thread(()->{//预期无打印traceIdlog.info("Thread::start");}).start();new Thread(new MyRunnable() {@Overridepublic void doRun() {}}).start();new Thread(new MyRunnable() {@Overridepublic void doRun() {log.info("MyRunnable::doRun::2");}}).start();executorService.submit(new MyRunnable() {@Overridepublic void doRun() {log.info("MyRunnable::doRun::3");}});Future future = executorService.submit(new MyCallable() {@Overridepublic Object doCall() {log.info("MyCallable::doCall");return "11111";}});log.info("MyCallable::call::{}",future.get());/*//没有通过子线程去创建会导致traceId丢失new MyRunnable(){@Overridepublic void doRun() {log.info("MyRunnable::doRun::");}}.run();String s = MDC.get(BaseConstants.TRACE_ID);MyCallable myCallable = new MyCallable() {@Overridepublic Object doCall() {log.info("MyCallable::doCall");return "11111";}};myCallable.call();
*/return ""+ MDC.get(BaseConstants.TRACE_ID);
}

打印如下:
在这里插入图片描述


总结

通过以上步骤,我们可以在Java项目中实现日志链路功能,提升项目的可维护性和调试效率。然而,日志链路只是日志管理的一个方面,我们还需要关注日志的级别控制、格式化、异步处理等方面,以构建一个完善的日志管理体系。当前的只是一个很简单的例子,从基础到深层次,慢慢的熟悉才能一步步的成长。

基于单体项目的日志链路,其实分布式也是可以套用的。像常见的调用下游系统或者分布式项目中的其他项目的业务接口,只需要在请求业务接口之前,将traceId放在请求头中或者是一个约定俗成的对象字段中,那么下游接口就可以获取到这个链路id并做处理,这样子当发现问题时,需要下游帮忙排查时只需要提供这个链路id,就可以更快的进行请求和错误定位。当然,分布式链路追踪肯定不止这么简单,但是原理都是一样的,只是功能更加完善,使用也比较方便。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/761794.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

适配器模式与桥接模式-灵活应对变化的两种设计策略大比拼

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自&#xff1a;设计模式深度解析&#xff1a;适配器模式与桥接模式-灵活应对变…

Spark Rebalance hint的倾斜的处理(OptimizeSkewInRebalancePartitions)

背景 本文基于Spark 3.5.0 目前公司在做小文件合并的时候用到了 Spark Rebalance 这个算子&#xff0c;这个算子的主要作用是在AQE阶段的最后写文件的阶段进行小文件的合并&#xff0c;使得最后落盘的文件不会太大也不会太小&#xff0c;从而达到小文件合并的作用&#xff0c;…

sentinel熔断规则详解

1、慢调用降级熔断 1.1、参数详解 最大RT&#xff1a;调用接口的最大时间。 比例阈值&#xff1a;超过了最大RT调用时间的请求的比例。 熔断时长&#xff1a;触发熔断后&#xff0c;熔断的时间 最小请求数据&#xff1a;每秒最少的请求数量&#xff0c;只有大于等于这个数…

SQLiteC/C++接口详细介绍sqlite3_stmt类(九)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍sqlite3_stmt类&#xff08;六&#xff09; 下一篇&#xff1a; 无 33、sqlite3_column_table_name 函数 sqlite3_column_table_name 用于返回结果集中指定列所属的表的名称。如果查询中列使…

前端案例:产品模块

文章目录 产品模块效果结构布局分析父级盒子布局图片和段落评价和详情 产品模块效果 结构布局分析 1、大的父级盒子包含全部的内容 2、内容装入 图片&#xff08;img标签&#xff09;&#xff1b;分别三个子盒子装入两段评价以及商品信息。 父级盒子布局 div {width: 300px…

网络通信——IP地址、端口号、协议(TCP、UDP)

通信架构 网络通信三要素 IP地址 IPv4地址 IPv6地址 IP域名 IP常识 端口号 概念 协议 开放式网络互联标准&#xff1a;OSI、TCP/IP 传输层的2个通信协议——UDP、TCP TCP协议&#xff1a;三次握手建立建立可靠连接 进行三次握手的原因&#xff1a;为了确保客户端和服务端…

实时数仓之实时数仓架构(Doris)

目前比较流行的实时数仓架构有两类,其中一类是以Flink+Doris为核心的实时数仓架构方案;另一类是以湖仓一体架构为核心的实时数仓架构方案。本文针对Flink+Doris架构进行介绍,这套架构的特点是组件涉及相对较少,架构简单,实时性更高,且易于Lambda架构实现,Doris本身可以支…

R语言Meta分析核心技术:回归诊断与模型验证

R语言作为一种强大的统计分析和绘图语言&#xff0c;在科研领域发挥着日益重要的作用。其中&#xff0c;Meta分析作为一种整合多个独立研究结果的统计方法&#xff0c;在R语言中得到了广泛的应用。通过R语言进行Meta分析&#xff0c;研究者能够更为准确、全面地评估某一研究问题…

安卓studio连接手机之后,一两秒之后就自动断开了。问题解决。

太坑了&#xff0c;安卓studio链接手机之后。几秒之后就断开了。我以为是adb的问题&#xff0c;就重新安装了一下adb。并且在环境变量中配置了Path的路径。然而并没有什么用啊。 后来查看是wps的服务和ADB有冲突。直接把WPS卸载掉之后就没有出现链接手机闪现的的问题。

基于python+vue研究生志愿填报辅助系统flask-django-php-nodejs

二十一世纪我们的社会进入了信息时代&#xff0c;信息管理系统的建立&#xff0c;大大提高了人们信息化水平。传统的管理方式对时间、地点的限制太多&#xff0c;而在线管理系统刚好能满足这些需求&#xff0c;在线管理系统突破了传统管理方式的局限性。于是本文针对这一需求设…

阿里云国际该如何设置DDoS高防防护策略?

DDoS高防提供针对网络四层DDoS攻击的防护策略设置功能&#xff0c;例如虚假源和空连接检测、源限速、目的限速&#xff0c;适用于优化调整非网站业务的DDoS防护策略。在DDoS高防实例下添加端口转发规则&#xff0c;接入非网站业务后&#xff0c;您可以单独设置某个端口的DDoS防…

Hive SQL必刷练习题:留存率问题(*****)

留存率&#xff1a; 首次登录算作当天新增&#xff0c;第二天也登录了算作一日留存。可以理解为&#xff0c;在10月1号登陆了。在10月2号也登陆了&#xff0c;那这个人就可以算是在1号留存 今日留存率 &#xff08;今日登录且明天也登录的用户数&#xff09; / 今日登录的总…

蓝桥杯STM32 G431 hal库开发速成——输入捕获

蓝桥杯的输入捕获较为简单&#xff0c;基本不涉及溢出的问题。所以这里就不介绍溢出了。文末有源码。 一、Cubemx配置 二、代码编写 1.在捕获回调函数中 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {if(htim->InstanceTIM3){switch(count){case 1:{jishu1…

如何让uni-app开发的H5页面顶部原生标题和小程序的顶部标题不一致?

如何让标题1和标题2不一样&#xff1f; 修改根目录下的App.vue&#xff08;核心代码如下&#xff09; <script>export default {onLaunch() {// 监听各种跳转----------------------------------------[navigateTo, redirectTo, reLaunch, switchTab, navigateBack, ].…

【JSON2WEB】10 基于 Amis 做个登录页面login.html

【JSON2WEB】01 WEB管理信息系统架构设计 【JSON2WEB】02 JSON2WEB初步UI设计 【JSON2WEB】03 go的模板包html/template的使用 【JSON2WEB】04 amis低代码前端框架介绍 【JSON2WEB】05 前端开发三件套 HTML CSS JavaScript 速成 【JSON2WEB】06 JSON2WEB前端框架搭建 【J…

《云计算:数字时代的引擎》

在数字化时代&#xff0c;云计算技术以其强大的计算能力和灵活的应用方式&#xff0c;成为推动各行各业发展的引擎。本文将围绕云计算的技术进展、技术原理、行业应用案例、面临的挑战与机遇以及未来趋势进行详细探讨。 云计算的技术进展 云计算的技术进展涵盖了多个方面&…

python(django)之产品后台管理功能实现

1、添加新项目 在命令行输入以下代码 python manage.py startapp prroduct 2、添加路径和代码结构 在新项目目录下admin.py中加入以代码 from .models import Product class ProductAdmin(admin.ModelAdmin):list_display [product_name, product_desc,producter,created_…

基于Springboot的闲置图书分享(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的闲置图书分享&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&…

Linux服务器导出CPU和内存使用情况

Linux服务器默认存储一个月的CPU和内存记录&#xff0c;所在目录&#xff1a;/var/log/sa/&#xff0c;如下图所示 在此用sar命令来执行 sar是一个比较全面的性能监控工具&#xff0c;包括cpu、内存、磁盘和网络等信息&#xff0c;并且该命令会每10分钟自动保存一次硬件资源使用…

odoo扩展导出pdf功能

1. 说明: odoo原生导出功能扩展导出pdf文件功能, 如有额外需求请联系博主 2. 版本说明: odoo版本: odoo15 其他odoo版本未进行测试,如有需要自行测试 3. 地址: 该补丁代码放在github仓库, 地址: https://github.com/YSL-Alpaca/odoo_export_pdf 4. 改补丁依赖于第三方软件wkh…