记录一次stopwatchThreadLocal为空的问题及解法

@Component
@Slf4j
public class ApiTimeMonitorInterceptor extends HandlerInterceptorAdapter {private ThreadLocal<Stopwatch> stopwatchThreadLocal = new NamedThreadLocal<>("api_time_monitor");//外部使用public long getElapsedMs() {Stopwatch stopwatch = stopwatchThreadLocal.get();return stopwatchThreadLocal.get().elapsed(TimeUnit.MILLISECONDS);}//接口调用时进入方法前拦截处理@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {stopwatchThreadLocal.set(Stopwatch.createStarted());return true;}//处理完返回值后的其他处理@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {try {String t = TraceUtils.getT();String code = TraceUtils.getBStatusCode();if (StringUtils.isEmpty(t)) {t = QPitcherUtils.buildT(request.getServletPath());}if (StringUtils.contains(t, "healthcheck.html")) {return;}String realT = QPitcherUtils.buildT(request.getServletPath());Stopwatch stopwatch = stopwatchThreadLocal.get().stop();long timeSpend = stopwatch.elapsed(TimeUnit.MILLISECONDS);Monitor.recordOne(ApiLogAdvice.API_MONITOR_PREFIX + "request_Total");Monitor.recordOne(MonitorLogUtil.join(ApiLogAdvice.API_MONITOR_PREFIX, "request_Total"));Monitor.recordQuantile(MonitorLogUtil.join(ApiLogAdvice.API_MONITOR_PREFIX, t, "_Total"), timeSpend);if (StringUtils.isNotBlank(realT)) {Monitor.recordQuantile(MonitorLogUtil.join(ApiLogAdvice.API_MONITOR_PREFIX, realT, "_Total"), timeSpend);log.info("{}_Total:{} ms", realT, timeSpend);}if (StringUtils.isNotBlank(code)) {if (StringUtils.equals("0", code)) {Monitor.recordOne(ApiLogAdvice.API_MONITOR_PREFIX + "request_Success");Monitor.recordOne(MonitorLogUtil.join(ApiLogAdvice.API_MONITOR_PREFIX, "request_Success"));Monitor.recordOne(ApiLogAdvice.API_MONITOR_PREFIX + t + "_Success");Monitor.recordOne(MonitorLogUtil.join(ApiLogAdvice.API_MONITOR_PREFIX, t, "_Success"));if (StringUtils.isNotBlank(realT)) {Monitor.recordOne(ApiLogAdvice.API_MONITOR_PREFIX + realT + "_Success");Monitor.recordOne(MonitorLogUtil.join(ApiLogAdvice.API_MONITOR_PREFIX, realT, "_Success"));}} else {Monitor.recordOne(ApiLogAdvice.API_MONITOR_PREFIX + "request_Fail");Monitor.recordOne(MonitorLogUtil.join(ApiLogAdvice.API_MONITOR_PREFIX, "request_Fail"));Monitor.recordOne(ApiLogAdvice.API_MONITOR_PREFIX + t + "_Fail");Monitor.recordOne(MonitorLogUtil.join(ApiLogAdvice.API_MONITOR_PREFIX, t, "_Fail"));Monitor.recordOne(ApiLogAdvice.API_MONITOR_PREFIX + t + "_Fail_" + code);Monitor.recordOne(MonitorLogUtil.join(ApiLogAdvice.API_MONITOR_PREFIX, t, "_Fail_", code));if (StringUtils.isNotBlank(realT)) {Monitor.recordOne(ApiLogAdvice.API_MONITOR_PREFIX + realT + "_Fail");Monitor.recordOne(MonitorLogUtil.join(ApiLogAdvice.API_MONITOR_PREFIX, realT, "_Fail"));Monitor.recordOne(ApiLogAdvice.API_MONITOR_PREFIX + realT + "_Fail_" + code);Monitor.recordOne(MonitorLogUtil.join(ApiLogAdvice.API_MONITOR_PREFIX, realT, "_Fail_", code));}}}log.info("{}_Total:{} ms", t, timeSpend);} finally {stopwatchThreadLocal.remove();TraceUtils.endTrace();}}
}

stopwatchThreadLocal 的使用方式

@ControllerAdvice
@Slf4j
public class ApiLogAdvice implements ResponseBodyAdvice {@Resourceprivate ApiTimeMonitorInterceptor apiTimeMonitorInterceptor;static final String API_MONITOR_PREFIX = "api_monitor_";private static final Logger RECOMMEND_LOGGER = LoggerFactory.getLogger("11111");@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if (body instanceof ResponseVO) {ResponseVO responseVO = (ResponseVO) body;responseVO.setTraceId(String.valueOf(TraceUtils.getValue("qtraceid")));TraceUtils.setBStatusCode(String.valueOf(responseVO.getBstatus().getCode()));handleReturnValue(responseVO, request);String t = TraceUtils.getT();String realT = StringUtils.EMPTY;String servletPath = request.getURI().getPath();if (StringUtils.isNotBlank(servletPath)) {realT = QPitcherUtils.buildT(servletPath);}Monitor.recordOne(API_MONITOR_PREFIX + t + "_request");Monitor.recordOne(MonitorLogUtil.join(API_MONITOR_PREFIX, t, "_request"));if (StringUtils.isNotBlank(realT)) {Monitor.recordOne(API_MONITOR_PREFIX + realT + "_request");Monitor.recordOne(MonitorLogUtil.join(API_MONITOR_PREFIX, realT, "_request"));}}return body;}/*** 记录业务日志** @param responseVO*/private void handleReturnValue(ResponseVO responseVO, ServerHttpRequest request) {String resJson = JsonUtil.toJson(responseVO);RECOMMEND_LOGGER.info("time={}####uri={}####param={}####result:{}",apiTimeMonitorInterceptor.getElapsedMs(), request.getURI().getPath().replace("/", "_"),TraceUtils.getRequestParam(), resJson);Monitor.recordQuantile("handleReturnValue_time", apiTimeMonitorInterceptor.getElapsedMs());}
}

 apiTimeMonitorInterceptor.getElapsedMs() 时发生空指针异常

排查路径:

1、确认调用 getElapsedMs 方法时位于 preHandle 方法和 afterCompletion 方法之间,

因为preHandle时setstart,再获取时先确认已经放入stopwatchThreadLocal 了

其次afterCompletion 有对stopwatchThreadLocal 的remove操作,因此需要确认是否再获取前被移除了

排查结果:确认调用 getElapsedMs 方法时位于 preHandle 方法和 afterCompletion 方法之间没有问题

2、调用时的线程是否再同一个线程内

排查结果:id都是38.但仍然无法获取到正确的 Stopwatch 实例

3、确认下拦截器的使用方式,时注入还是其他。以及所有涉及到改拦截器的配置

a、确认直接调用放是否时注入

b、确认拦截器配置是否是注入方式

排查解决:再配置拦截器的addInterceptors 方式里 使用的是new ApiTimeMonitorInterceptor ,改为注入方式

@Configuration
public class WebAdapter implements WebMvcConfigurer {@Resource(name = "apiTimeMonitorInterceptor")private ApiTimeMonitorInterceptor apiTimeMonitorInterceptor;/*** 拦截器注册* @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {//众多的拦截器组成了一个拦截器链/*** 主要方法说明:* addPathPatterns 用于添加拦截规则* excludePathPatterns 用户排除拦截*/registry.addInterceptor(apiTimeMonitorInterceptor).addPathPatterns("/api/**").excludePathPatterns("/");}}

问题解决,感谢观看

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

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

相关文章

2023华数杯数学建模C题思路 - 母亲身心健康对婴儿成长的影响

# 1 赛题 C 题 母亲身心健康对婴儿成长的影响 母亲是婴儿生命中最重要的人之一&#xff0c;她不仅为婴儿提供营养物质和身体保护&#xff0c; 还为婴儿提供情感支持和安全感。母亲心理健康状态的不良状况&#xff0c;如抑郁、焦虑、 压力等&#xff0c;可能会对婴儿的认知、情…

【雕爷学编程】MicroPython动手做(28)——物联网之Yeelight 5

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

43. 字符串相乘(leetcode刷题记录)

字符串相乘 给定两个以字符串形式表示的非负整数 num1 和 num2&#xff0c;返回 num1 和 num2 的乘积&#xff0c;它们的乘积也表示为字符串形式。 注意&#xff1a;不能使用任何内置的 BigInteger 库或直接将输入转换为整数。 class Solution { public:string multiply(stri…

UniPro助力金融企业数字化转型 强化项目协作与跟踪

根据一份来自Standish Group的研究报告&#xff08;"CHAOS Report"&#xff09;&#xff0c;该报告对美国各行业的项目进行了调查&#xff0c;结果显示仅有不到一半&#xff08;约44%&#xff09;的项目能够成功按时完成&#xff0c;并达到预期的业务目标。其中&…

计算两条直线夹角(C++)

计算两条直线的锐角可以使用向量的知识来实现。在C中&#xff0c;我们可以定义一个函数来计算两个向量的夹角&#xff0c;并根据夹角的余弦值来判断角度的大小。以下是一个用C编写的示例代码&#xff1a; #include <iostream> #include <cmath>using namespace st…

c++调用ffmpeg api录屏 并进行rtmp推流

代码及工程见https://download.csdn.net/download/daqinzl/88156528 开发工具&#xff1a;visual studio 2019 记得启动rtmp流媒体服务 nginx的rtmp服务见https://download.csdn.net/download/daqinzl/20478812 播放&#xff0c;采用ffmpeg工具集里的ffplay.exe, 执行命令 f…

数智保险 创新未来 | GBASE南大通用亮相中国保险科技应用高峰论坛

本届峰会以“数智保险 创新未来”为主题&#xff0c;GBASE南大通用携新一代创新数据库产品及金融信创解决方案精彩亮相&#xff0c;与国内八百多位保险公司高管和众多保险科技公司技术专家&#xff0c;就保险领域数字化的创新应用及生态建设、新一代技术突破及发展机遇、前沿科…

深入理解TCP三次握手:连接可靠性与安全风险

目录 导言TCP简介和工作原理的回顾TCP三次握手的目的和步骤TCP三次握手过程中可能出现的问题和安全风险为什么TCP三次握手是必要的&#xff1f;是否可以增加或减少三次握手的次数&#xff1f;TCP四次挥手与三次握手的异同点 导言 在网络通信中&#xff0c;TCP&#xff08;Tra…

centos 重启 nginx 的三种方式

重启nginx的方式都有哪些&#xff0c;ChatGPT给出了比较全面的答案 1.service nginx restart 2.systemctl restart nginx 3.cd /usr/local/nginx/sbin 停止&#xff1a;./nginx -s stop 启动: ./nginx 重新加载配置: ./nginx -s reload 注意&#xff1a;cd /usr/local/nginx/s…

Redis入门-1

简介 nosql的一种&#xff0c;不是替代传统的sql&#xff0c;而是对传统的sql进行补充增强。redis用于短时间的高访问&#xff0c;其数据是存储在内存上的。 应用场景&#xff1a; 缓存 任务队列 消息队列 分布式锁 Linux系统上安装Redis 运行redis,进入/usr/local/redis-…

Linux: openssh: sshd_config: useprivilegeseparation; sshd [priv]

下面这个链接是比较老的一个讨论,是关于配置选项:useprivilegeseparation; https://community.hpe.com/t5/operating-system-hp-ux/ssh-child-process-hanging-and-cannot-be-killed/td-p/5042853 从目前openssh的说明看,这个将要被废弃的一个配置,不建议再使用。而且从m…

uC-OS2 V2.93 STM32L476 移植:串口打印篇

前言 前几篇已经 通过 STM32CubeMX 搭建了 NUCLEO-L476RG 的 STM32L476RG 的 裸机工程&#xff0c;下载了 uC-OS2 V2.93 的源码&#xff0c;并把 uC-OS2 的源文件加入 Keil MDK5 工程&#xff0c;通过适配 Systick 系统定时器与 PendSV 实现任务调度&#xff0c;初步让 uC-OS2 …

在腾讯云服务器OpenCLoudOS系统中安装redis(有图详解)

创建安装目录&#xff1a; mkdir -p /app/soft/redis 2. 下载安装包 进入安装目录 cd /app/soft/redis/ 下载安装包 wget https://download.redis.io/releases/redis-7.0.1.tar.gz 解压&#xff1a; tar -zxvf redis-7.0.1.tar.gz 安装gcc yum install gcc-c 进入re…

LUN映射出错导致写操作不互斥的服务器数据恢复案例

服务器数据恢复环境&#xff1a; 某公司的光纤SAN存储系统&#xff0c;6块硬盘组建一组RAID6&#xff0c;划分若干LUN&#xff0c;MAP到不同的SOLARIS操作系统服务器上。 服务器故障&分析&#xff1a; 由于业务增长需要新增应用&#xff0c;工作人员增加了一台IBM服务器&am…

QT--day6(人脸识别、图像处理)

人脸识别&#xff1a; /***********************************************************************************头文件****************************************************************************************/#ifndef WIDGET_H #define WIDGET_H#include <QWidget>…

新手入门Jenkins自动化部署入门详细教程

1. 背景 在实际开发中&#xff0c;我们经常要一边开发一边测试&#xff0c;当然这里说的测试并不是程序员对自己代码的单元测试&#xff0c;而是同组程序员将代码提交后&#xff0c;由测试人员测试&#xff1b; 或者前后端分离后&#xff0c;经常会修改接口&#xff0c;然后重新…

【机器学习】西瓜书习题3.4Python编程比较 10 折交叉验证法和留一法所估计出的对率回归的错误率

3.4 选择两个 UCI 数据集&#xff0c;比较 10 折交叉验证法和留一法所估计出的对率回归的错误率. 参考代码 结合自己的理解&#xff0c;添加注释。 数据集链接&#xff0c;下载后的数据在后缀名是data的文件中&#xff0c;使用记事本打开&#xff0c;本次解题需要去掉第一行属…

云主机测试Flink磁盘满问题解决

问题描述&#xff1a; 使用云主机测试Flink时&#xff0c;根目录满了。 经排查发现运行Flink任务后根目录空间一直在减少&#xff0c;最后定位持续增加的目录是/tmp目录 解决方法&#xff1a; 修改Flink配置使用一个相对较大的磁盘目录做为Flink运行时目录 # Override the…

为什么vscode访问谷歌浏览器是显示白色????

1、我的代码没有错误: 2、访问谷歌浏览器就显示白色&#xff1f;&#xff1f;&#xff1f;是什么情况

教资学习笔记总结

科目一 科目二 第一章 教育基础知识和基本原理 第一节 教育的认识 1.教育的概念 教育的词源&#xff1a;教育一词最早出现于《孟子尽心上》&#xff1a;“得天下英才而教育之”许慎在《说文解字》中最早解释教育&#xff1a;“教&#xff0c;上所施&#xff0c;下所效也”…