Log4j-tag丢失

news2024/12/23 7:41:39

一、引言

        最近有个线上日志丢失tag的问题,是组内封装了后置请求的拦截器把请求的响应结果存到ClickHouse里面去,但是日志总有一些tag丢失。

        作者提出父级线程的threadlocal被清空,同事认为可能是threadlocal的弱引用在gc的时候被回收。两种想法其实都是有可能的,那就要一个个验证。这里要感谢ld和cbc的共同排查、讨论,最后锁定原因是父级线程的MDC被ar组件清空。

二、问题

1、架构背景

        这里先讲讲问题后面架构的背景,由于需要按照特定的格式存一些日志数据到ck,组内封装的后置拦截器会使用slf4j的MDC.getCopyOfContextMap()获取请求被存储在ThreadLocal里面的map,里面放了一些订单、人员信息的key-value,拿到这些数据再和一些响应参数存入ck。

        集团的日志框架里面实现了servlet的Filter,在servlet容器初始化的时候会运行这个拦截器,把一部分埋点运行,但是会清空,保证进入业务处理的时候是干净的。真正用到的买点是在业务线程里面实现的,所以在业务处理完成到后置拦截器的过程中可能存在clear的操作。需要把框架整体结构研究下。

2、问题背景

        在落数据的监控,看到有几列偶现是空的。

        看代码确认这些空列都是从MDC取出来的。

三、分析

        快速的讨论锁定了出问题的代码。

1、代码

        这个代码是从父线程取出来日志信息,作为入参返回给子runnable,等到子runnable启动的时候,它就变成了子线程里面的threadlocal。

@Override
    public void apply(PostResponseFilterArgs args) {
        try {
            Runnable logTask = () -> doLog(args);
            if (CommonLogConfig.logAsync()
                && StringUtilsExt.isNotBlank(CommonLogConfig.logExecutor())) {
                TaskRunner.create(LogTagsWrap.wrap(logTask)).start(CommonLogConfig.logExecutor());
            } else {
                logTask.run();
            }
        } catch (Throwable e) {
            LOGGER.info(TITLE, e);
        }
    }
public static Runnable wrap(Runnable runnable) {
        Map<String, String> logTags = log.getCommonTags();
        return () -> {
            log.setCommonTags(logTags);
            runnable.run();
        };
    }

private static void doLog(PostResponseFilterArgs args) {
        // 构建业务信息
        Map<String, String> map = LogUtil.buildService(args);
        // 设置MDC信息
        LogUtil.setMdc(map);
        // 记录CK日志
        Throwable throwable = args.executionResult().throwable();
        if (Objects.nonNull(throwable)) {
            LogUtil.error(map, throwable, CommonLogConfig.logScenario());
        } else {
            LogUtil.info(map, null, CommonLogConfig.logScenario());
        }

    }

  2、浅拷贝✖️

        作者一开始以为这里父级线程取出来的可能是浅拷贝,然后在子线程启动之前被其他请求使用了,导致并发清空。但是追溯下去,MDC使用的是深拷贝拿到日志信息。

@Override
    public Map<String, String> getCopy() {
        final StringMap map = localMap.get();
        return map == null ? new HashMap<>() : map.toMap();
    }

 @Override
    public Map<String, String> getCopy() {
        final Map<String, String> map = localMap.get();
        return map == null ? new HashMap<>() : new HashMap<>(map);
    }
public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        if (s > 0) {
            if (table == null) { // pre-size
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            else if (s > threshold)
                resize();
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict);
            }
        }
    }

3、子级threadlocal在gc时回收✖️  

        不过子线程里面一定是在set mdc信息的时候是空的,也就引出来开头作者和同事的两个猜想。threallocal作者感觉是不太可能的,因为线程执行完才算是强引用链结束,但是还是写个测试方法试一试。

        使用system.gc强制回收,看看会不会把threadlocal给回收,结果并没有。这里要设置启动参数,不然system.gc不一定会执行。

public static void main(String[] args) {

List<Task> list = new ArrayList<>();
 for (int i = 0; i < 10000;i++) {
System.out.println(i + "start");
 Map<String, String> md = new HashMap<>();
 md.put("orderid",i + "666");
 // 设置MDC信息
 MDC.setContextMap(md);
 int finalI = i;
 Runnable logTask = () -> {
// 构建SOA信息
 Map<String, String> map = new HashMap<>();
 map.put("soa", finalI + "666");
 System.gc();
 // 设置MDC信息
 LogUtil.setMdc(map);
 System.out.println(map);
 if (map.size() == 1) {
System.out.println(finalI + "soaerror");
 }
};
 Task t = TaskRunner.create(LogTagsWrap.wrap(logTask));
 list.add(t);
 System.out.println(i + "end");
 }
for (Task t : list) {
t.start(CommonLogConfig.logExecutor());
 }
}

4、父级MDC信息清空☑️

        那么接下来就是看会不会清空父级线程的threadlocal了,作者把框架整体看了一遍。感觉出问题的地方还不少,就看这些拦截器是不是会异步走清理。

         cbc使用过arthas进行线上诊断,作者也学习了一波,使用watch对全限定名的类方法进行字节码动态生成,打印方法的出入参便于排查。

        但是作者这里的情况比较特殊,由于需要确定父级丢失还是子级的gc回收,需要在两个方法里面取出MDC的log信息,在为空的时候打印出来。作者和cbc折腾了许久,比较复杂的监控arthas还是不好用的。就在这时候ld给出了新的方向。

5、ar组件清空父级MDC信息☑️

        他发现出问题的机器ip固定在某两台机器上面,于是我们开始分析这两天机器与其他机器有什么不同。cbc之前接触过测开组做了一个ar组件,使用java-agengt方法在服务发布时注入,这是我们观察不到但是有可能进行拦截增强的代码。

        于是我们开始研究他的源码,果然这个组件使用了 SerivceInstrumentation extends TypeInstrumentation类进行字节码增强,并且是在业务处理的方法外层进行增强,比后置拦截器早。

@Override
    protected ElementMatcher<TypeDescription> typeMatcher() {
        return named("server.OperationHandler");
    }

private static void recordMocker(Mocker mocker) {
        // 业务处理
        MDC.clear();
    }

        他的过程可以画成这样。

        关掉ar组件之后,可以看到没有再发生有问题的ck日志记录,了解到ar组件的执行频率是一小时10次,这也说明了为什么我们发现的监控是部份发生,其实这个组件导致了必然的结果。

        一切的偶然总有必然的原因!

 四、总结

        再次抒感:一切的偶然总有必然的原因!这次的问题告诉了我们,问题不一定出现在看得见的代码,java-ageng之类看不见的代码很多时候是另外一个方向。

        这里再次感谢ld和cbc的协作分析,合作无间!

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

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

相关文章

lmbench----lmbench性能测试工具迁移至openEuler操作系统实践

【lmbench----lmbench性能测试工具迁移至openEuler操作系统实践】 文章目录 一、openEuler系统上编译部署与运行1.1 安装基础依赖1.2 下载 lmbench 源码1.3 编译安装1.4 执行 lmbench 测试1.5 结果查看 二、lmbench 性能测试结果解析2.1 处理器性能2.2 数学运算性能2.3 上下文切…

左移测试,如何确保安全合规还能实现高度自动化?

「云原生安全既是一种全新安全理念&#xff0c;也是实现云战略的前提。 基于蚂蚁集团内部多年实践&#xff0c;云原生PaaS平台SOFAStack发布完整的软件供应链安全产品及解决方案&#xff0c;包括静态代码扫描Pinpoint&#xff0c;软件成分分析SCA&#xff0c;交互式安全测试IA…

2023年【道路运输企业安全生产管理人员】试题及解析及道路运输企业安全生产管理人员复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 道路运输企业安全生产管理人员试题及解析考前必练&#xff01;安全生产模拟考试一点通每个月更新道路运输企业安全生产管理人员复审考试题目及答案&#xff01;多做几遍&#xff0c;其实通过道路运输企业安全生产管理…

基于Docker使用Minikube

1. 查看并操控Minikube状态信息 Minikube相当于docker中的一个container&#xff0c;可以在Docker Desktop中看到并操控Minikube container的相关状态&#xff1a; 通过以下命令查看当前docker中的container&#xff1a; % docker ps CONTAINER ID IMAGE …

Win10搜索栏卡住无响应可以尝试以下方法

msdt.exe -ep WindowsHelp id SearchDiagnostic 参考&#xff1a; Fix problems in Windows Search - Windows Client | Microsoft Learn

Vue 项目中如何使用Bootstrap5(简单易懂)

Vue 项目中如何使用Bootstrap5&#xff08;简单易懂&#xff09; 安装在 src/main.js 文件下引入包在vue文件中使用 Bootstrap官网&#xff08;中文&#xff09;&#xff1a;https://www.bootcss.com/ Bootstrap5文档&#xff1a;https://v5.bootcss.com/docs/getting-started/…

Django添加csrf保护机制

步骤 要在Django中启用CSRF保护&#xff0c;您可以按照以下步骤进行操作&#xff1a; 1. 在Django的settings.py文件中&#xff0c;确保django.middleware.csrf.CsrfViewMiddleware中间件已添加到MIDDLEWARE设置中。通常&#xff0c;这个中间件默认就会包含在其中。 2. 在HTM…

测试计划驱动开发模式 TPDD:一种比 TDD 更友好的开发模式

相信大部分开发团队都在使用TDD&#xff0c;并且还有很多开发团队都 对外声明 在使用 TDD 开发模式。 之所以说是“对外声明”&#xff0c;是因为很多开发团队虽然号称使用的是 TDD 开发模式&#xff0c;实际开发过程中却无法满足 TDD 的要求。 实际上&#xff0c;测试驱动的…

安科瑞AMC200多回路智能电量采集监控装置在隧道中的应用-安科瑞黄安南

1项目背景厂家直供黄安南1876-15//06-237 河南安阳林州市某高速公路项目是河南省政府主要打造的一项公路建设项目&#xff0c;该项目全长约70公里&#xff0c;起点位于安阳市内&#xff0c;终点位于林州市县。该项目总投资约60亿元人民币&#xff0c;建设工期预计为3年。 该项…

微信管理系统!

多微信聚合聊天 这个功能可以让你在一个页面上轻松切换多个微信&#xff0c;方便与多个客户进行沟通。这样可以避免在多个手机或电脑界面之间切换&#xff0c;提高工作效率。 群发功能 微信登陆上系统后&#xff0c;可以对好友/群进行群发消息&#xff08;支持文字&#xff0…

基于深度学习的人脸表情识别 计算机竞赛

文章目录 0 前言1 技术介绍1.1 技术概括1.2 目前表情识别实现技术 2 实现效果3 深度学习表情识别实现过程3.1 网络架构3.2 数据3.3 实现流程3.4 部分实现代码 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习的人脸表情识别 该项目较…

电脑频繁出现程序无响应怎么办?

在Windows系统中每个运行的应用程序都会有一个进程&#xff0c;而每个进程会有多个线程&#xff0c;就如你打开微信同时与多个人聊天&#xff0c;而每个线程只能同时处理一个线程。若Windows向程序传递消息时&#xff0c;而程序在处理其他工作未作出反应&#xff0c;Windows就会…

Hadoop RPC简介

数新网络-让每个人享受数据的价值https://www.datacyber.com/ 前 言 RPC&#xff08;Remote Procedure Call&#xff09;远程过程调用协议&#xff0c;一种通过网络从远程计算机上请求服务&#xff0c;而不需要了解底层网络技术的协议。RPC它假定某些协议的存在&#xff0c;例…

二进制搭建 Kubernetes与k8s集群搭建(一)

目录 二进制搭建 Kubernetes v1.20 操作系统初始化配置 部署 docker引擎 部署 etcd 集群 准备签发证书环境 在 master01 节点上操作 生成Etcd证书 在 node01 节点上操作 在 node02 节点上操作 部署 Master 组件 在 master01 节点上操作 部署 Worker Node …

企业通关必备,iPaaS应该这样搭建

iPaaS是指集成平台即服务&#xff08;Integration Platform as a Service&#xff09;&#xff0c;是一种云计算服务&#xff0c;提供了一套工具和服务来帮助企业集成不同的应用程序、数据和系统。iPaaS也可以通过云端的方式&#xff0c;将企业内部的应用程序与云端应用程序进行…

nodejs使用axios以formdata形式上传图片

nodejs使用axios以formdata形式上传图片 FormData是一种用于发送表单数据的接口&#xff0c;它可以用来上传文件。在前端&#xff0c;可以通过创建一个FormData对象&#xff0c;将要上传的文件添加到这个对象中&#xff0c;然后通过AJAX请求将这个FormData对象发送给服务器。服…

unity性能优化__Statistic状态分析

在Unity的Game视图右上角&#xff0c;我们会看到有Stats选项&#xff0c;点击会出现这样的信息 我使用的Unity版本是2019.4.16 一、Audio&#xff0c;顾名思义是声音信息 1&#xff1a;Level:-74.8dB 声音的相对强度或音量。通常&#xff0c;音量级别以分贝&#xff08;dB&a…

超2000个大模型应用,支持文心4.0!AI Studio星河大模型社区升级上新

想给自己做个私人定制的旅行攻略&#xff0c;满足个性化的出游需求&#xff0c;还要细致关注到天气、穿衣、老人孩子的作息等等&#xff0c;但太耗时费力怎么办&#xff1f;让AI帮忙搞定。一位开发者在AI Studio星河大模型社区用短短数小时就做好了“旅行规划家”智能应用。像这…

睿趣科技:抖音开网店真的能相信吗

随着互联网的发展&#xff0c;越来越多的人开始尝试在网上开店。抖音作为一款短视频平台&#xff0c;近年来也逐渐成为了一个热门的电商平台。然而&#xff0c;关于抖音开网店是否真的能相信的问题&#xff0c;一直存在争议。 首先&#xff0c;我们需要了解抖音作为一个电商平台…

科幻类小说,探索科幻巨作,开启无限遐想,感受未知的奇妙世界

如果你渴望探索未来的无尽可能性&#xff0c;感受未知的魅力&#xff0c;那么小郑为你推荐三本科幻小说。这些书籍将带你进入一个充满惊喜的世界&#xff0c;让你对未来充满期待。 《星舰流浪文明》 这是一本硬科幻小说&#xff0c;讲述了星舰流浪文明的故事。这个文明在宇宙中…