自定义方法耗时监控告警

自定义方法耗时监控告警

用于记录代码耗时,当代码耗时超过指定阈值时打印告警日志

在这里插入图片描述

自定义注解

通过自定义注解的方式可以更方便的使用,只需要在需要做耗时兼容的方法上增加上该注解即可

package com.huakai.springenv.aspect.profiler;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTimeProfiler {
}

自定义切面

需要注意的时这里使用Spring aop实现,如果是是使用this(或者省略)调用bean内部方法将导致代理失效,进而导致监控失效

package com.huakai.springenv.aspect.profiler;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TimeProfileAspect {

    @Around("execution(* *.*(..)) && @annotation(com.huakai.springenv.aspect.profiler.DBTimeProfiler)")
    public Object catTransactionAspect(ProceedingJoinPoint joinPoint) throws Throwable{
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        DBTimeProfiler timeProfiler = signature.getMethod().getAnnotation(DBTimeProfiler.class);
        if(timeProfiler == null){
            return joinPoint.proceed();
        }
       String methodName = signature.getMethod().getName();
        DBTimeProfile.enter(signature.getMethod().getDeclaringClass().getSimpleName() + "." + methodName);

        try{
            return joinPoint.proceed();
        }finally{
            DBTimeProfile.release();
        }
    }

}

自定义处理

package com.huakai.springenv.aspect.profiler;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * 用于记录代码耗时,当代码耗时超过指定阈值时打印告警日志
 */
public class DBTimeProfile {

    private static final Logger log = LoggerFactory.getLogger(DBTimeProfile.class);
    private static final ThreadLocal<LinkedList<TimeProfileElement>> logs = new ThreadLocal<>();
    private static final ThreadLocal<TimeProfileElement> currentElement = new ThreadLocal<>();
    private static final ThreadLocal<HttpServletRequest> request = new ThreadLocal<>();
    private static final ThreadLocal<Integer> currentThreshold = new ThreadLocal<>();
    private static volatile int threshold = 100;// 超时时间,单位毫秒
    private static final String timeout = "DBTimeProfile timeout ";

    private DBTimeProfile() {
    }

    private static void enter() {
        enter("");
    }

    /**
     * 在需要监控耗时的方法体前调用
     *
     * @param tag 方法体的名字
     */
    public static void enter(String tag) {
        if (logs.get() == null) {
            return;
        }
        TimeProfileElement cur = currentElement.get();

        TimeProfileElement t = new TimeProfileElement();
        t.logEnterTime();
        t.setTag(tag);
        t.parent = cur;
        t.setDeep(cur == null ? 0 : cur.getDeep() + 1);
        currentElement.set(t);

        logs.get().add(t);
    }

    /**
     * 在需要监控耗时的方法体后调用
     */
    public static void release() {
        LinkedList<TimeProfileElement> s1 = logs.get();
        TimeProfileElement cur = currentElement.get();
        if (s1 == null || cur == null) {
            return;
        }
        if (s1.size() <= 1) {
            // throw new IllegalStateException("release failed,will stop this timeProfile, enter/release 必须成对出现");
            log.warn("release failed,will stop this timeProfile, enter/release 必须成对出现", new IllegalStateException());
            clear();
            return;
        }
        cur.logReleaseTime();
        currentElement.set(cur.parent);
    }

    /**
     * 在Filter等入口处调用,开始计时
     */
    public static void start() {
        logs.set(new LinkedList<>());
        enter();
    }
    /**
     * 在Filter等入口处调用,开始计时
     */
    public static void start(HttpServletRequest httpServletRequest) {
        logs.set(new LinkedList<>());
        request.set(httpServletRequest);
        enter();

    }

    /**
     * 在Filter等入口处调用,结束当前计时
     */
    public static void end() {
        end("undefined");
    }

    /**
     * 在Filter等入口处调用,结束当前计时,如果超过日志,会打印告警日志
     */
    public static void end(String methodName) {
        TimeProfileElement cur = currentElement.get();
        if (cur == null) {
            return;
        }
        if (cur.parent != null) {
            log.warn("timeProfile methodName:{} failed, enter/release 必须成对出现", methodName);
            clear();
            return;
        }
        cur.logReleaseTime();

        long timeConsume = cur.getCostTimeMillis();
        Integer threshold = currentThreshold.get();
        if (threshold == null) {
            threshold = DBTimeProfile.threshold;
        }
        if (cur.getCostTimeMillis() > threshold) {
            // 输出日志
            StringBuilder sb = new StringBuilder();
            HttpServletRequest httpRequest = request.get();
            if (httpRequest != null) {
                sb.append(timeout).append(timeConsume).append("ms >").append(threshold).append("ms, url=").append(httpRequest.getRequestURI());
            } else {
                sb.append(timeout).append(timeConsume).append("ms > methodName:").append(methodName);
            }
            List<TimeProfileElement> list = new ArrayList<>(logs.get());

            for (TimeProfileElement s : list) {
                sb.append("\r\n\t");
                for (int i = 0; i < s.getDeep(); i++) {
                    sb.append("-");
                }
                Long consume = s.getCostTimeMillis();
                sb.append(consume * 100 / timeConsume).append("%");
                sb.append("  ").append(consume).append("ms");
                if (s.getTag() != null) {
                    sb.append("  ").append(s.getTag());
                }
            }
            log.warn("{}", sb);
        }
        clear();
    }

    private static void clear() {
        request.remove();
        currentElement.remove();
        logs.remove();
        currentThreshold.remove();
    }

    /**
     * 设置报警阈值,这是全局配置,只要在应用启动时设置一次即可,单位:ms
     * <br/>
     *
     * @param threshold 告警阈值
     */
    public static void setThreshold(int threshold) {
        DBTimeProfile.threshold = threshold;
    }

    /**
     * 设置当前线程调用栈的报警阈值,这个配置只会在当前线程、本次监控生效。
     *
     * @param threshold 告警阈值
     */
    public static void setCurrentThreshold(int threshold) {
        currentThreshold.set(threshold);
    }

    /**
     * 获取当前线程的报警报警阈值
     *
     * @return 告警阈值
     */
    public static Integer getCurrentThreshold() {
        return currentThreshold.get();
    }

    private static class TimeProfileElement {

        // 基线时间,为了减小timeMillis的占用空间
        private static final long BASE_TIME = System.currentTimeMillis();

        private int timeMillis;
        private int deep;
        private String tag;

        private TimeProfileElement parent;

        void logEnterTime() {
            timeMillis = (int) (System.currentTimeMillis() - BASE_TIME);
        }

        void logReleaseTime() {
            timeMillis = (int) (System.currentTimeMillis() - BASE_TIME - timeMillis);
        }

        long getCostTimeMillis() {
            return timeMillis;
        }

        int getDeep() {
            return deep;
        }

        void setDeep(int deep) {
            this.deep = deep;
        }

        String getTag() {
            return tag;
        }

        void setTag(String tag) {
            this.tag = tag;
        }

    }
}

测试

测试类
package com.huakai.springenv.service.impl.timeprofiler;

import com.huakai.springenv.aspect.profiler.DBTimeProfiler;
import com.huakai.springenv.utils.SleepUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;


@Service
@Slf4j
public class TimeProfilerService {

    /**
     * 引入Spring代理过后的对象,否则无法被aop增强
     */
    @Resource
    private TimeProfilerService self;


    @DBTimeProfiler
    public void method() {
        SleepUtils.workTime(200);
        log.info("{}#{} completed", getClass().getName(), Thread.currentThread().getStackTrace()[1].getMethodName());
        self.method2();
    }

    @DBTimeProfiler
    public void method2() {
        SleepUtils.workTime(400);
        log.info("{}#{} completed", getClass().getName(), Thread.currentThread().getStackTrace()[1].getMethodName());
        self.method3();
    }

    @DBTimeProfiler
    public void method3() {
        SleepUtils.workTime(600);
        log.info("{}#{} completed", getClass().getName(), Thread.currentThread().getStackTrace()[1].getMethodName());
    }
}
测试方法
package com.huakai.springenv.service;

import com.huakai.springenv.aspect.profiler.DBTimeProfile;
import com.huakai.springenv.service.impl.timeprofiler.TimeProfilerService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
@SpringBootTest
class TimeProfilerServiceTest {

    @Resource
    private TimeProfilerService timeProfilerService;


    @Test
    void timeProfilerTest() {
        try {
            DBTimeProfile.start();
            timeProfilerService.method();
        }finally {
            DBTimeProfile.end( Thread.currentThread().getStackTrace()[1].getMethodName());
        }

    }
}
结果
2024-07-14 20:06:41.499  INFO 13900 --- [           main] c.h.s.s.i.t.TimeProfilerService          : com.huakai.springenv.service.impl.timeprofiler.TimeProfilerService#method completed
2024-07-14 20:06:41.900  INFO 13900 --- [           main] c.h.s.s.i.t.TimeProfilerService          : com.huakai.springenv.service.impl.timeprofiler.TimeProfilerService#method2 completed
2024-07-14 20:06:42.501  INFO 13900 --- [           main] c.h.s.s.i.t.TimeProfilerService          : com.huakai.springenv.service.impl.timeprofiler.TimeProfilerService#method3 completed
2024-07-14 20:06:42.501  WARN 13900 --- [           main] c.h.s.aspect.profiler.DBTimeProfile      : DBTimeProfile timeout 1209ms > methodName:timeProfilerTest
	100%  1209ms  
	-99%  1206ms  TimeProfilerService.method
	--82%  1002ms  TimeProfilerService.method2
	---49%  601ms  TimeProfilerService.method3

ps:DBTimeProfile.start() & DBTimeProfile.end()可以放到Web过滤器中,这样便可以更便捷的监控接口的耗时,监控接口耗时,也可以放到自定义线程中,监控线程执行耗时

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python与自动化脚本编写

Python与自动化脚本编写 Python因其简洁的语法和强大的库支持&#xff0c;成为了自动化脚本编写的首选语言之一。在这篇文章中&#xff0c;我们将探索如何使用Python来编写自动化脚本&#xff0c;以简化日常任务。 一、Python自动化脚本的基础 1. Python在自动化中的优势 Pyth…

i18n、L10n、G11N 和 T9N 的含义

注&#xff1a;机翻&#xff0c;未校对。 Looking into localization for the first time can be terrifying, if only due to all of the abbreviations. But the meaning of i18n, L10n, G11N, and T9N, are all very easy to understand. 第一次研究本地化可能会很可怕&…

Leetcode3202. 找出有效子序列的最大长度 II

Every day a Leetcode 题目来源&#xff1a;3202. 找出有效子序列的最大长度 II 解法1&#xff1a;动态规划 本题是选与不选的子序列问题&#xff0c;可以尝试给出这样的状态定义&#xff1a; dp[i][j]&#xff1a;以 nums[i] 结尾模 k 后值为 j 的最长子序列的长度。 那么…

el-popover或el-popconfirm中button不展示问题

vue3在使用Element-plus 2.X时&#xff0c;出现el-popover或el-popconfirm中button不展示问题。 正常效果&#xff1a; 第一种错误原因&#xff1a;el-button没有添加 slotreference <template slot-scope"scope"><el-popconfirm title"您确定删除吗…

【Linux】从零开始认识多线程 --- 线程控制

在这个浮躁的时代 只有自律的人才能脱颖而出 -- 《觉醒年代》 从零开始认识多线程 --- 线程控制 1 知识回顾2 线程控制2.1 线程创建2.2 线程等待2.3 线程终止 3 测试运行3.1 小试牛刀 --- 创建线程3.2 探幽析微 --- 理解线程参数3.3 小有心得 --- 探索线程返回3.4 求索无厌 …

CSS技巧专栏:一日一例 2.纯CSS实现 多彩边框按钮特效

大家好,今天是 CSS技巧一日一例 专栏的第二篇《纯CSS实现多彩边框按钮特效》 先看图: 开工前的准备工作 正如昨日所讲,为了案例的表现,也处于书写的习惯,在今天的案例开工前,先把昨天的准备工作重做一遍。 清除浏览器的默认样式定义页面基本颜色设定body的样式清除butt…

好用的智能模型网站合集——Vol1

探秘 AIGC 精彩应用&#xff0c;开启 AI 无限可能 别忘了点赞关注转发&#xff01; openxlab 在线工具合集 大眼仔好用工具合集 扣子——海量ai工具合集

书生大模型实战营-入门岛-第三关

提交PR 建立仓库 https://github.com/Olive-2019/NL2SQL/tree/main

算法日常练习

对于这个题&#xff0c;如何处理同一个方向的问题&#xff0c;且对于同一组的如果间隔太大如何实现离散化 #include<bits/stdc.h> using namespace std;#define int long long typedef long long ll; map<pair<int,int>,vector<pair<ll,ll>>> mp…

Windows安装adb和常用操作命令

简介 ADB&#xff08;Android Debug Bridge&#xff09;是Android开发者、测试工程师和普通用户在管理、调试、自动化控制Android设备时的重要工具。它提供了丰富的命令集&#xff0c;允许通过命令行接口对Android设备进行各种操作。 下载 https://download.csdn.net/downlo…

TCA链路聚合技术之手工配置详解

stp端口状态 1. discarding堵塞状态&#xff1a;禁用&#xff0c;堵塞&#xff0c;监听 所有接口初始状态&#xff0c;无法发送数据帧&#xff0c;也无法学习mac地址表&#xff0c;最终只有AP口永久停留该状态。DP和RP会向下一个状态转变&#xff0c; 2、learning学习状态&a…

【C++进阶学习】第七弹——AVL树——树形结构存储数据的经典模块

二叉搜索树&#xff1a;【C进阶学习】第五弹——二叉搜索树——二叉树进阶及set和map的铺垫-CSDN博客 目录 一、AVL树的概念 二、AVL树的原理与实现 AVL树的节点 AVL树的插入 AVL树的旋转 AVL树的打印 AVL树的检查 三、实现AVL树的完整代码 四、总结 前言&#xff1a…

2024世界人工智能大会(WAIC)学习总结

1 前言 在2024年的世界人工智能大会&#xff08;WAIC&#xff09;上&#xff0c;我们见证了从农业社会到工业社会再到数字化社会的深刻转变。这一进程不仅体现在技术的单点爆发&#xff0c;更引发了整个产业链的全面突破&#xff0c;未来将是技术以指数级速度发展的崭新时代。…

21天学通C++:第十一、十二章节

第十一章&#xff1a;多态 多态基础 多态&#xff08;Polymorphism&#xff09;是面向对象语言的一种特征&#xff0c;让您能够以类似的方式处理不同类似的对象。 有一句话总结的很好&#xff1a;多态其实就是父类型引用指向子类型对象。 为什么需要多态 #include <ios…

Profibus协议转Profinet协议网关模块连接智能电表通讯案例

一、背景 在工业自动化领域&#xff0c;Profibus协议和Profinet协议是两种常见的工业通讯协议&#xff0c;而连接智能电表需要用到这两种协议之间的网关模块。本文将通过一个实际案例&#xff0c;详细介绍如何使用Profibus转Profinet模块&#xff08;XD-PNPBM20&#xff09;实…

docker 安装 onlyoffice

1.文档地址 Installing ONLYOFFICE Docs for Docker on a local server - ONLYOFFICE 2.安装onlyoffice docker run -i -t -d -p 9000:8000 --restartalways -e JWT_ENABLEDfalse onlyoffice/documentserver 如果发现镜像无法下载,可以尝试更换镜像源 {"registry-mir…

wifi信号处理的CRC8、CRC32

&#x1f9d1;&#x1f3fb;个人简介&#xff1a;具有3年工作经验&#xff0c;擅长通信算法的MATLAB仿真和FPGA实现。代码事宜&#xff0c;私信博主&#xff0c;程序定制、设计指导。 &#x1f680;wifi信号处理的CRC8、CRC32 目录 &#x1f680;1.CRC概述 &#x1f680;1.C…

【链表】算法题(一) ---- 力扣 / 牛客

一、移除链表元素 移除链表中值为val的元素&#xff0c;并返回新的头节点 思路&#xff1a; 题目上这样说&#xff0c;我们就可以创建一个新的链表&#xff0c;将值不为val的节点&#xff0c;尾插到新的链表当中&#xff0c;最后返回新链表的头节点。 typedef struct ListNo…

[React 进阶系列] useSyncExternalStore hook

[React 进阶系列] useSyncExternalStore hook 前情提要&#xff0c;包括 yup 的实现在这里&#xff1a;yup 基础使用以及 jest 测试 简单的提一下&#xff0c;需要实现的功能是&#xff1a; yup schema 需要访问外部的 storage外部的 storage 是可变的React 内部也需要访问同…

linux adb命令

⏩ 大家好哇&#xff01;我是小光&#xff0c;正在努力寻找自己的职业方向。 ⏩ 在调试设备时&#xff0c;经常会用到adb命令&#xff0c;本文对linux adb命令做一个知识分享。 ⏩ 感谢你的阅读&#xff0c;不对的地方欢迎指正。 1.adb命令 即 Android Debug Bridge 是一种允许…