首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何快速过滤出一次请求的所有日志?

如何快速过滤出一次请求的所有日志?

作者头像
lyb-geek
发布于 2019-06-17 11:44:13
发布于 2019-06-17 11:44:13
1.2K00
代码可运行
举报
文章被收录于专栏:Linyb极客之路Linyb极客之路
运行总次数:0
代码可运行

示例源码地址:https://github.com/wudashan/slf4j-mdc-muti-thread

前言

在现网出现故障时,我们经常需要获取一次请求流程里的所有日志进行定位。如果请求只在一个线程里处理,则我们可以通过线程ID来过滤日志,但如果请求包含异步线程的处理,那么光靠线程ID就显得捉襟见肘了。

华为IoT平台,提供了接收设备上报数据的能力, 当数据到达平台后,平台会进行一些复杂的业务逻辑处理,如数据存储,规则引擎,数据推送,命令下发等等。由于这个逻辑之间没有强耦合的关系,所以通常是异步处理。如何将一次数据上报请求中包含的所有业务日志快速过滤出来,就是本文要介绍的。

正文

SLF4J日志框架提供了一个MDC(Mapped Diagnostic Contexts)工具类,谷歌翻译为映射的诊断上下文,从字面上很难理解,我们可以先实战一把。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Main {

    private static final String KEY = "requestId";
    private static final Logger logger = LoggerFactory.getLogger(Main.class);
    
    public static void main(String[] args) {

        // 入口传入请求ID
        MDC.put(KEY, UUID.randomUUID().toString());
        
        // 打印日志
        logger.debug("log in main thread 1");
        logger.debug("log in main thread 2");
        logger.debug("log in main thread 3");

        // 出口移除请求ID
        MDC.remove(KEY);

    }

}

我们在main函数的入口调用MDC.put()方法传入请求ID,在出口调用MDC.remove()方法移除请求ID。配置好log4j2.xml文件后,运行main函数,可以在控制台看到以下日志输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
2018-02-17 13:19:52.606 {requestId=f97ea0fb-2a43-40f4-a3e8-711f776857d0} [main] DEBUG cn.wudashan.Main - log in main thread 1
2018-02-17 13:19:52.609 {requestId=f97ea0fb-2a43-40f4-a3e8-711f776857d0} [main] DEBUG cn.wudashan.Main - log in main thread 2
2018-02-17 13:19:52.609 {requestId=f97ea0fb-2a43-40f4-a3e8-711f776857d0} [main] DEBUG cn.wudashan.Main - log in main thread 3

从日志中可以明显地看到花括号中包含了(映射的)请求ID(requestId),这其实就是我们定位(诊断)问题的关键字(上下文)。有了MDC工具,只要在接口或切面植入put()和remove()代码,在现网定位问题时,我们就可以通过grep requestId=xxx *.log快速的过滤出某次请求的所有日志。

进阶

然而,MDC工具真的有我们所想的这么方便吗?回到我们开头,一次请求可能涉及多线程异步处理,那么在多线程异步的场景下,它是否还能正常运作呢?Talk is cheap, show me the code。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Main {

    private static final String KEY = "requestId";
    private static final Logger logger = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) {

        // 入口传入请求ID
        MDC.put(KEY, UUID.randomUUID().toString());

        // 主线程打印日志
        logger.debug("log in main thread");

        // 异步线程打印日志
        new Thread(new Runnable() {
            @Override
            public void run() {
                logger.debug("log in other thread");
            }
        }).start();

        // 出口移除请求ID
        MDC.remove(KEY);

    }

}

代码里我们新起了一个异步线程,并在匿名对象Runnable的run()方法打印日志。运行main函数,可以在控制台看到以下日志输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
2018-02-17 14:05:43.487 {requestId=e6099c85-72be-4986-8a28-de6bb2e52b01} [main] DEBUG cn.wudashan.Main - log in main thread
2018-02-17 14:05:43.490 {} [Thread-1] DEBUG cn.wudashan.Main - log in other thread

不幸的是,请求ID在异步线程里不打印了。这是怎么回事呢?要解决这个问题,我们就得知道MDC的实现原理。由于篇幅有限,这里就暂不详细介绍,MDC之所以在异步线程中不生效是因为底层采用ThreadLocal作为数据结构,我们调用MDC.put()方法传入的请求ID只在当前线程有效。感兴趣的小伙伴可以自己深入一下代码细节。

知道了原理那么解决这个问题就轻而易举了,我们可以使用装饰器模式,新写一个MDCRunnable类对Runnable接口进行一层装饰。在创建MDCRunnable类时保存当前线程的MDC值,在执行run()方法时再将保存的MDC值拷贝到异步线程中去。代码实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MDCRunnable implements Runnable {

    private final Runnable runnable;

    private final Map<String, String> map;

    public MDCRunnable(Runnable runnable) {
        this.runnable = runnable;
        // 保存当前线程的MDC值
        this.map = MDC.getCopyOfContextMap();
    }

    @Override
    public void run() {
        // 传入已保存的MDC值
        for (Map.Entry<String, String> entry : map.entrySet()) {
            MDC.put(entry.getKey(), entry.getValue());
        }
        // 装饰器模式,执行run方法
        runnable.run();
        // 移除已保存的MDC值
        for (Map.Entry<String, String> entry : map.entrySet()) {
            MDC.remove(entry.getKey());
        }
    }
    
}

接着,我们需要对main函数里创建的Runnable实现类进行装饰:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Main {

    private static final String KEY = "requestId";
    private static final Logger logger = LoggerFactory.getLogger(Main.class);
    private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();

    public static void main(String[] args) {

        // 入口传入请求ID
        MDC.put(KEY, UUID.randomUUID().toString());

        // 主线程打印日志
        logger.debug("log in main thread");

        // 异步线程打印日志,用MDCRunnable装饰Runnable
        new Thread(new MDCRunnable(new Runnable() {
            @Override
            public void run() {
                logger.debug("log in other thread");
            }
        })).start();

        // 异步线程池打印日志,用MDCRunnable装饰Runnable
        EXECUTOR.execute(new MDCRunnable(new Runnable() {
            @Override
            public void run() {
                logger.debug("log in other thread pool");
            }
        }));
        EXECUTOR.shutdown();

        // 出口移除请求ID
        MDC.remove(KEY);

    }

}

执行main函数,将会输出以下日志:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
2018-03-04 23:44:05.343 {requestId=5ee2a117-e090-41d8-977b-cef5dea09d34} [main] DEBUG cn.wudashan.Main - log in main thread
2018-03-04 23:44:05.346 {requestId=5ee2a117-e090-41d8-977b-cef5dea09d34} [Thread-1] DEBUG cn.wudashan.Main - log in other thread
2018-03-04 23:44:05.347 {requestId=5ee2a117-e090-41d8-977b-cef5dea09d34} [pool-2-thread-1] DEBUG cn.wudashan.Main - log in other thread pool

Congratulations!经过我们的努力,最终在异步线程和线程池中都有requestId打印了!

总结

本文讲述了如何使用MDC工具来快速过滤一次请求的所有日志,并通过装饰器模式使得MDC工具在异步线程里也能生效。有了MDC,再通过AOP技术对所有的切面植入requestId,就可以将整个系统的任意流程的日志过滤出来。使用MDC工具,在开发自测阶段,可以极大地节省定位问题的时间,提升开发效率;在运维维护阶段,可以快速地收集相关日志信息,加快分析速度。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-06-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linyb极客之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
一文探索“预训练”的奥秘!
2022年下半年开始,涌现出一大批“大模型”的优秀应用,其中比较出圈的当属AI作画与ChatGPT,刷爆了各类社交平台,其让人惊艳的效果,让AI以一个鲜明的姿态,站到了广大民众面前,让不懂AI的人也能直观地体会到AI的强大。大模型即大规模预训练模型,本文就和大家聊一聊 预训练模型的起源与发展。
Datawhale
2023/01/10
1.4K0
一文探索“预训练”的奥秘!
一网打尽:14种预训练语言模型大汇总
预训练语言模型是NLP中的核心之一,在pretrain-finetune这一阶段的NLP发展中发挥着举足轻重的作用。预训练语言模型的无监督训练属性,使其非常容易获取海量训练样本,并且训练好的语言模型包含很多语义语法知识,对于下游任务的效果会有非常明显的提升。本文首先介绍预训练语言模型的里程碑方法,然后进一步介绍学术界针对预训练语言模型中的问题提出的各种改进和创新,包括14个经典预训练语言模型。
圆圆的算法笔记
2022/09/22
1.7K0
一网打尽:14种预训练语言模型大汇总
[预训练语言模型专题] XLNet:公平一战!多项任务效果超越BERT
感谢清华大学自然语言处理实验室对预训练语言模型架构的梳理,我们将沿此脉络前行,探索预训练语言模型的前沿技术,红框中为已介绍的文章,绿框中为本期介绍的XLNet,欢迎大家留言讨论交流。
朴素人工智能
2020/05/27
5100
从BERT、XLNet到MPNet,细看NLP预训练模型发展变迁史
来自 | 知乎 地址 | https://zhuanlan.zhihu.com/p/146325984
朴素人工智能
2020/06/15
1.8K0
从BERT、XLNet到MPNet,细看NLP预训练模型发展变迁史
开发 | 谷歌更强NLP模型XLNet开源:20项任务全面碾压BERT!
与基于自回归语言建模的预训练处理方法相比,基于自编码的预训练处理方法(比如BERT)具有良好的双向上下文建模能力。然而,由于依赖于使用掩码破坏输入,BERT忽略了掩码位置之间的依赖关系,并出现了预训练-微调(pretrain-finetune)差异。
AI科技评论
2019/06/21
8050
开发 | 谷歌更强NLP模型XLNet开源:20项任务全面碾压BERT!
预训练模型超全知识点梳理与面试必备高频FAQ
预训练模型(Pre-trained Models,PTMs)的出现将NLP带入了一个全新时代。2020年3月18日,邱锡鹏老师发表了关于NLP预训练模型的综述《Pre-trained Models for Natural Language Processing: A Survey》,这是一篇全面的综述,系统地对PTMs进行了归纳分类。
zenRRan
2020/10/26
2.3K0
预训练模型超全知识点梳理与面试必备高频FAQ
(含源码)「自然语言处理(NLP)」RoBERTa&&XLNet&&语言模型&&问答系统训练
本次内容主要包括:鲁棒优化Bert模型(RoBERTa)、自回归预训练模型(XLNet)、无监督多任务学习语言模型、生成预训练语言理解、深层上下文单词表示、键值记忆网络、大规模问答系统训练等 。(全部含源码)
ShuYini
2020/07/31
9540
(含源码)「自然语言处理(NLP)」RoBERTa&&XLNet&&语言模型&&问答系统训练
20项任务全面碾压BERT,CMU全新XLNet预训练模型屠榜(已开源)
2018 年,谷歌发布了基于双向 Transformer 的大规模预训练语言模型 BERT,刷新了 11 项 NLP 任务的最优性能记录,为 NLP 领域带来了极大的惊喜。很快,BERT 就在圈内普及开来,也陆续出现了很多与它相关的新工作。
机器之心
2019/06/21
6140
20项任务全面碾压BERT,CMU全新XLNet预训练模型屠榜(已开源)
原创 | 从ULMFiT、Transformer、BERT等经典模型看NLP 发展趋势
自然语言处理(Natural Language Process,简称NLP)是计算机科学、信息工程以及人工智能的子领域,专注于人机语言交互,探讨如何处理和运用自然语言。自然语言处理的研究,最早可以说开始于图灵测试,经历了以规则为基础的研究方法,流行于现在基于统计学的模型和方法,从早期的传统机器学习方法,基于高维稀疏特征的训练方式,到现在主流的深度学习方法,使用基于神经网络的低维稠密向量特征训练模型。
数据派THU
2020/11/03
1.1K0
原创 | 从ULMFiT、Transformer、BERT等经典模型看NLP 发展趋势
【NLP】XLNet详解
BERT本身很有效,但它也存在一些问题,比如不能用于生成、以及训练数据和测试数据的不一致(Discrepancy)。在本文中,我们重点介绍比BERT更强大的预训练模型XLNet,它为了达到真正的双向学习,采用了Permutation语言模型、以及使用了双流自注意力机制,并结合了Transformer-XL的相对位置编码。
yuquanle
2020/03/13
1.4K0
【每周NLP论文推荐】从预训练模型掌握NLP的基本发展脉络
读论文是做AI的人必需要下的功夫,所以咱们开通了专栏《每周NLP论文推荐》。本着有三AI的一贯原则,即系统性学习,所以每次的论文推荐也会是成系统的,争取每次能够把一个领域内的“故事”基本说清楚。
用户1508658
2019/08/01
7980
【每周NLP论文推荐】从预训练模型掌握NLP的基本发展脉络
[预训练语言模型专题] ENRIE(Tsinghua):知识图谱与BERT相结合,为语言模型赋能助力
5-8:[BERT来临]、[浅析BERT代码]、[ERNIE合集]、[MT-DNN(KD)]
朴素人工智能
2020/06/29
2K0
BERT微调效果不佳?不如试试这种大规模预训练模型新范式
BERT模型自发布至今已整整两年了,但是其余热仍未消减。从一经问世的轰动,简单应用/微调便可达到某个领域或者任务的SOTA效果;到如今的各种『被吊打』,BERT巨人肩膀上的新宠大致可以分为以下这么几类:
NewBeeNLP
2020/12/08
1.8K0
自然语言预训练模型大总结​
先来一张图。 本文主要援引复旦大学邱锡鹏教授的论文:NLP预训练模型综述,对预训练模型进行了一些梳理
机器学习之禅
2022/07/11
8860
自然语言预训练模型大总结​
【NLP】Dive into BERT:语言模型与知识
最近在看的主要是跟知识相关的一些东西,包括回顾了一些知识表示模型呀,一些大规模的语言模型如何锦上添花融入外部知识的方法呀,如果你感兴趣的话可以直接去之前几篇文章里面瞄一眼。今天就以 知识 为切入点来更深入地剖析一下最近比较火的预训练模型。
zenRRan
2019/12/06
8980
一文了解预训练语言模型!
现有的神经网络在进行训练时,一般基于后向传播(Back Propagation,BP)算法,先对网络中的参数进行随机初始化,再利用随机梯度下降(Stochastic Gradient Descent,SGD)等优化算法不断优化模型参数。
guichen1013
2022/09/22
1.1K0
一文了解预训练语言模型!
XLNet预训练模型,看这篇就够了!(代码实现)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
mantch
2019/09/30
7300
XLNet预训练模型,看这篇就够了!(代码实现)
拆解XLNet模型设计,回顾语言表征学习的思想演进
深度学习的基本单元是向量。我们将建模对象对应到各自的向量 x (或者一组向量 x{1}, x{2}, ..., x{n}),然后通过变换、整合得到新的向量 h,再基于向量 h 得到输出的判断 y。这里的 h 就是我们说的表征 (Representation),它是一个向量,描述了我们的建模对象。而语言表征学习就是解决怎么样将一个词、一句话、一篇文章通过变换 (Transformation) 和整合 (Aggregation) 转化成对应的向量 h 的问题。
机器之心
2019/07/11
6770
拆解XLNet模型设计,回顾语言表征学习的思想演进
3 天,我把 NLP 中的预训练模型、图神经网络、模型压缩、知识图谱彻底撸清楚了!
大家都知道NLP近几年非常火,而且发展也特别快。那些耳熟的BERT、GPT-3、图神经网络、知识图谱等技术实际上也就是这几年发展起来的,特别像图神经网络在这两年间取得了飞速的发展。 我们正处在信息爆炸的时代、面对每天铺天盖地的新的网络资源和论文、很多时候我们面临的问题并不是缺资源,而是找准资源并高效学习。但很多时候你会发现,花费大量的时间在零零散散的内容上,但最后发现效率极低,浪费了很多宝贵的时间。 为了迎合大家学习的需求,我们重磅推出了《自然语言处理训练营》(一定要看到最后),主要有两个目的: 1. 对
zenRRan
2022/06/14
7540
3 天,我把 NLP 中的预训练模型、图神经网络、模型压缩、知识图谱彻底撸清楚了!
[预训练语言模型专题] RoBERTa: 捍卫BERT的尊严
5-8:[BERT来临]、[浅析BERT代码]、[ERNIE合集]、[MT-DNN(KD)]
朴素人工智能
2020/06/21
6K0
推荐阅读
相关推荐
一文探索“预训练”的奥秘!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档