springboot实现接口请求日志自动生成(日志自动埋点)

news2024/12/22 23:19:20

文章目录

  • 1.作用:
  • 2.原理:
  • 3.代码:
    • 一.config层
    • 二. mq层 :
    • 三.service层:
  • 4.效果图
  • 5.声明

1.作用:

springboot接口请求日志自动生成,实现接口日志自动埋点生成
1.统一日志生成格式;—方便查看
2.汇总请求参数和请求结果一次性输出日志数据 ,方便查询问题,节省查询请求问题时间;—很直观的日志,前后端问题排查快
3.通过日志自动生成减少编写日志时间,减少人力成本;—省编码时间
4.记录用户行为轨迹,记录接口时间,为后续风险监控,用户行为统计分析做铺垫;—记录数据

2.原理:

通过面向切面编程的形式,在不影响原有项目的业务(包括加解密)的同时,进行日志埋点

代码
配置模块

3.代码:

一.config层

1.bean类型调度

/**
 * 获取spring bean
 *
 */
@Component
public class SpringContextAware implements ApplicationContextAware {
    /**
     * Spring应用上下文
     */
    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextAware.context = applicationContext;
    }
    /**
     * 获取Spring应用上下文
     * @return
     */
    public static ApplicationContext getApplicationContext() {
        return context;
    }

    /**
     * 获取对象
     * @param name
     * @return
     * @throws BeansException
     */
    public static Object getBean(String name) throws BeansException {
        return context.getBean(name);
    }
}

2.核心逻辑

ps:其中的ApiOperation 是swagger的注解,可以自定义注解实现自己的参数配置,
这里只是为了方便使用已有的swagger的ApiOperation 注解 的value(接口说明)和notes(接口发布说明)的数据

/**
 *
 * 监控埋点
 * 打印请求和响应信息
 */
@Aspect
@Component
public class WebLogAspect {
    @Autowired
    private RedisTokenLoader redisTokenLoader;

    @Autowired
    private MqProducer mqProducer;


    // 拿到日志对象
    //    slf4j的日志对象
    private final Logger log = LoggerFactory.getLogger(WebLogAspect.class);

    @Pointcut("execution( * com.jt.saas..*Controller.*(..))")
    public void webLog() {

    }

    /**
     * 读取会话中的token去取redis缓存信息
     * 有效期受控于redis.timeout
     *
     * @return
     */
    protected LoginInfoVo readLoginInfo(HttpServletRequest request) {
        String token = request.getHeader(HeaderConstant.HEADER_MINI_TOKEN);
        if (token == null) {
            // 非拦截接口的token校验
            String authorization = request.getHeader(HttpConstant.AUTHORIZATION);
            if (authorization != null) {
                if (authorization.length() > 7) {
                    token = authorization.substring(7);
                }
            }
        }

        if (StringUtils.isBlank(token)) {
            return null;
        }

        return redisTokenLoader.readToken(token);
    }

    // 切点实现
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        // 记录开始时间
        long start = System.currentTimeMillis();
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        assert servletRequestAttributes != null;
        HttpServletRequest request = servletRequestAttributes.getRequest();
        LoginInfoVo loginInfo = readLoginInfo(request);
        // 获取方法名
        String className = pjp.getTarget().getClass().getName();
        // 获取执行的方法名称
        String methodName = pjp.getSignature().getName();

        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);

        // 定义返回参数
        Object result = null;


        // 获取方法入参
        // Object[] param = pjp.getArgs();
        // String requestBody = convertFormToString(request);


        // 执行目标方法
        result = pjp.proceed();

        ObjectMapper objectMapper = new ObjectMapper();
        String bodyData = null;
        try {
            bodyData = objectMapper.writeValueAsString(result);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

        JSONObject  param= convertFormToJson(request);

        LogVo logVo=new LogVo();
        logVo.setUserId(loginInfo != null&&loginInfo.getMemberId()!=null ? loginInfo.getMemberId() : null);
        logVo.setUserName(loginInfo != null&&loginInfo.getNickname2()!=null ? loginInfo.getNickname2() : null);
        logVo.setMobile(loginInfo != null&&loginInfo.getMobile()!=null ? loginInfo.getMobile() : null);
        logVo.setModelName(apiOperation != null&&apiOperation.value()!=null ?apiOperation.value(): null);
        logVo.setRemark(apiOperation != null&&apiOperation.notes()!=null ?apiOperation.notes(): null);
        logVo.setUsedTime(System.currentTimeMillis() - start);
        logVo.setParamData(param);
        logVo.setResultData(bodyData.length() > 2000 ? "数据太大截取2000数据:"+bodyData.substring(0, 2000) :result);
        logVo.setMethodName(className+"."+methodName);
        logVo.setIp(request.getRemoteAddr());
        logVo.setUrlData(request.getRequestURL().toString());
        // 日志输出
        log.info("userRequest:{}",JSON.toJSONString(logVo));
        //每个项目自定义自己的mq去处理自定义日志统计或者分析行为
        mqProducer.sendDelay("MINI_LOGVO",JSON.toJSONString(logVo),100);

        return result;
    }



    private String convertFormToString(HttpServletRequest request) {
        Map<String, String> result = new HashMap<>(8);
        Enumeration<String> parameterNames = request.getParameterNames();
        while (parameterNames.hasMoreElements()) {
            String name = parameterNames.nextElement();
            result.put(name, request.getParameter(name));
        }
        try {
            return JSON.toJSONString(result);
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    private JSONObject convertFormToJson(HttpServletRequest request) {
        try {

            JSONObject jsonObject = new JSONObject();
            Enumeration<String> parameterNames = request.getParameterNames();
            while (parameterNames.hasMoreElements()) {
                String name = parameterNames.nextElement();
                jsonObject.put(name, request.getParameter(name));
            }


            return jsonObject;
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }
}

二. mq层 :

异步处理日志通用逻辑,使用单例模式实现一个工厂实例,用Spring来获取具体的跟踪服务实现,以此来提供高度解耦和可扩展的服务注册与获取机制去处理指定接口自定义服务

@Component
public class TrackingMq {

    @Autowired
    private TrackingCommonService trackingCommonService;
    @RabbitListener(bindings = {@QueueBinding(value = @Queue(value = "MINI_LOGVO", durable = "true"), exchange =
    @Exchange(value = "saasExchange", type = "x-delayed-message"), key = "MINI_LOGVO")},containerFactory = "firstFactory")
    @RabbitHandler
    public void process(Message message) throws Exception {
        String msg = new String(message.getBody(), "UTF-8");
        LogVo logVo = JSON.parseObject(msg, LogVo.class);
        //通用的分析功能
        trackingCommonService.commonTracking(logVo);

        //自定义分析模块》按MethodName执行自定义功能;按需对接口进行自定义处理
        TrackingService trackingService= TrackingFactory.getInstance().get(logVo.getMethodName());
        if(trackingService!=null){
            trackingService.changeByTrackLog(logVo);
        }
    }

}

三.service层:

通用处理,自定义处理,单例工厂
1.单例工厂

**
 * 单例工厂类
 *
 * 追踪日志,触发自定义模块功能
 */
public class TrackingFactory {
    private Map<String, TrackingService> map= new HashMap<>();
    public static class Holder {
        public static TrackingFactory instance = new TrackingFactory();

    }

    public static TrackingFactory getInstance() {
        return Holder.instance;
    }

    public TrackingService get(String methodName) {
        return map.get(methodName);
    }

    public TrackingFactory() {
        TrackingService listRankActivityRuleTrackingServiceImpl = (ListRankActivityRuleTrackingServiceImpl) SpringContextAware.getBean("listRankActivityRuleTrackingServiceImpl");

        map.put("com.jt.saas.mini.controller.activity.ActivityController.listRankActivityRule", listRankActivityRuleTrackingServiceImpl);

    }
}

2.通用日志处理

public interface TrackingCommonService {
    public void commonTracking(LogVo logVo);
}

@Service
public class TrackingCommonServiceImpl implements TrackingCommonService{
    private final Logger log = LoggerFactory.getLogger(TrackingCommonServiceImpl.class);
    public void outTimeCount(LogVo logVo){
        if(logVo.getUsedTime()>1000){
            log.error("日志分析:超时1s的接口:{},{}",logVo.getModelName(),logVo.getUsedTime());
        }
    }

    @Override
    public void commonTracking(LogVo logVo) {
        outTimeCount(logVo);
    }
}

3.自定义指定接口日志处理

public interface TrackingService {
    /**
     * 日志触发自定义内容:数据分析,数据统计,风险监控,发送mq,保存数据入库等等
     * @param logVo
     */
    void changeByTrackLog(LogVo logVo);
}

可以增加无数个自定义指定接口日志处理实现自己的处理日志方式

@Service
public class ListRankActivityRuleTrackingServiceImpl implements TrackingService {
    private final Logger log = LoggerFactory.getLogger(ListRankActivityRuleTrackingServiceImpl.class);
    private Map<String, Integer> map= new HashMap<>();

    @Override
    public void changeByTrackLog(LogVo logVo) {
        //发送mq
        //发送kafaka
        //发送数据库
        //统计用户访问次数
        Integer i=map.get(logVo.getMethodName());
        if(i==null){
            i=1;
        }else{
            i=i+1;
        }
        map.put(logVo.getMethodName(), i);

        log.info("日志分析:{}:访问次数:{}",logVo.getModelName(),i);
    }
}

4.效果图

日志自动埋点生成效果图
通用日志分析效果图

5.声明

本文内容均为Bright_ Chen原创,版权均属于Bright_ Chen所有,未经授权,不得转载、摘编、复制或建立镜像。我们尊重他人知识版权,如有引用、摘录,请务必注明来源及作者,否则我们将依法追究其侵权责任。

根据《中华人民共和国著作权法》、《信息网络传播权保护条例》等相关法律法规,任何单位或个人不得擅自复制、转载、链接、抓取或以其他方式使用本站内容;已授权或合作的单位或个人,应在授权范围内使用,并注明来源。

对于违反本声明者,Bright_ Chen保留追究其法律责任的权利。同时,对于恶意抄袭、盗版等侵犯版权的个人和组织,我们将采取包括但不限于法律诉讼、公开曝光等措施维护合法权益。

我们鼓励和支持合法合规的信息共享,对于希望合法使用本文内容的机构或个人,请事先通过以下联系方式与我们取得联系,获取合法授权:

联系邮箱:1024347104@qq.com
感谢您对原创内容的尊重与支持!

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

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

相关文章

day2 数组

977.有序数组的平方 有负数。暴力快排&#xff1a;先对每个元素平方 o(n)&#xff0c;再快排o&#xff08;nlogn&#xff09;&#xff08;先分区 o(n)&#xff0c;再递归排序 递归深度 logn&#xff09; class Solution:def sortedSquares(self, nums: List[int]) -> List…

控制欲过强的Linux小进程

控制欲强?视奸&#xff1f;普通人那才叫视奸&#xff0c;您是皇帝&#xff0c;天下大事无一逃过您的耳目&#xff0c;您想看什么就看什么&#xff0c;臣怀疑他在朋友圈私养兵士&#xff0c;囤积枪甲&#xff0c;蓄意谋反&#xff0c;图谋皇位啊&#xff01; 哈哈哈哈开个玩笑&…

C++ 类和对象 构造函数(下)

一 初始化列表&#xff1a; 1.1 构造函数体赋值&#xff1a; 在C中&#xff0c;构造函数用于创建对象并赋予其初始值。通常&#xff0c;我们可以在构造函数体内对成员变量进行赋值&#xff1a; class Date { public:Date(int year, int month, int day) {_year year;_mont…

常见的数据分析用例 —— 信用卡交易欺诈检测

文章目录 引言数据集分析1. 读入数据并快速浏览2.计算欺诈交易占数据集中交易总数的百分比3. 类别不平衡对模型的影响3.1 总体思路&#xff08;1&#xff09;数据的划分&#xff08;2&#xff09;训练模型&#xff08;3&#xff09;测试模型&#xff08;4&#xff09;解决不平衡…

知迪科技发布了全新软件产品

近日&#xff0c;知迪科技发布了全新软件产品——Vehicle Bus Tool-Trace Version免费版。该软件产品能高效的离线分析汽车总线数据&#xff0c;并拥有一大亮点功能&#xff1a;Ethernet通信离线文件基于ARXML文件的信号级解析&#xff0c;具体操作如下&#xff1a; 1、新建一…

git修改提交姓名

git config --global user.name “新用户名” git config --global user.email “新邮箱地址” 修改提交的用户名 git config --global user.name “yu***”

java转义文本中的HTML字符为安全的字

java转义文本中的HTML字符为安全的字 &#xff0c;以下字符被转义&#xff1a;替换为 (&apos; doesnt work in HTML4) " 替换为 &quot; & 替换为 &amp; < 替换为 < > 替换为 >1.先添加hutool依赖到pom <dependency><groupId>cn…

Docker安装笔记

1. Mac安装Docker 1.1 Docker安装包下载 1.1.1 阿里云 对于10.10.3以下的用户 推荐使用 对于10.10.3以上的用户 推荐使用 1.1.2 官网下载 系统和芯片选择适合自己的安装包 1.2 镜像加速 【推荐】阿里镜像 登陆后&#xff0c;左侧菜单选中镜像加速器就可以看到你的专属地…

阿尔泰科技利用485模块搭建自动灌溉系统实现远程控制

自动灌溉系统又叫土壤墒情监控系统&#xff0c;土壤墒情监控系统主要实现固定站无人值守情况下的土壤墒情数据的自动采集和无线传输&#xff0c;数据在监控中心自动接收入库&#xff1b;可以实现24小时连续在线监控并将监控数据通过有线、无线等传输方式实时传输到监控中心生成…

怎样在 PostgreSQL 中实现数据的异地备份?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 怎样在 PostgreSQL 中实现数据的异地备份&#xff1f;一、异地备份的重要性二、PostgreSQL 中的备份方…

Docker的安装【虚拟机】

Docker的安装【虚拟机】 准备环节【可跳过】 # 进入yum.repos.d目录&#xff0c;保留base.repo 和 CentOs-Base.repo&#xff0c;其余删除 cd /etc/yum.repos.d/ ----------------------------------------------------- # 现将base.repo 和 CentOs-Base.repo 存放至bak包中 …

SQL执行流程、SQL执行计划、SQL优化

select查询语句 select查询语句中join连接是如何工作的&#xff1f; 1、INNER JOIN 返回两个表中的匹配行。 2、LEFT JOIN 返回左表中的所有记录以及右表中的匹配记录。 3、RIGHT JOIN 返回右表中的所有记录以及左表中的匹配记录。 4、FULL OUTER JOIN 返回左侧或右侧表中有匹…

深度洞察市场需求,Vatee万腾平台定制化解决方案引领潮流

在当今这个日新月异的商业环境中&#xff0c;深刻理解并精准把握市场需求&#xff0c;已成为企业生存与发展的核心要义。Vatee万腾平台&#xff0c;作为行业内的佼佼者&#xff0c;凭借其卓越的定制化解决方案能力&#xff0c;正引领着市场潮流&#xff0c;为企业客户量身打造数…

【BUG】已解决:AttributeError: ‘DataFrame‘ object has no attribute ‘append‘

已解决&#xff1a;AttributeError: ‘DataFrame‘ object has no attribute ‘append‘ 目录 已解决&#xff1a;AttributeError: ‘DataFrame‘ object has no attribute ‘append‘ 【常见模块错误】 错误原因&#xff1a; 解决办法&#xff1a; 欢迎来到英杰社区https:/…

Linux—KVM虚拟化中(虚拟机克隆,快照,还原,删除)等应用实例

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f427;Linux基础知识(初学)&#xff1a;点击&#xff01; &#x1f427;Linux高级管理防护和群集专栏&#xff1a;点击&#xff01; &#x1f510;Linux中firewalld防火墙&#xff1a;点击&#xff01; ⏰️创作…

【数据结构初阶】顺序表三道经典算法题(详解+图例)

Hello&#xff01;很高兴又见到你了~~~ 看看今天要学点什么来充实大脑吧—— 目录 1、移除元素 【思路图解】 【总结】 2、删除有序数组中的重复项 【思路图解】 【总结】 3、合并两个有序数组 【思路图解】 【总结】 至此结束&#xff0c;Show Time&#xff01; 1、…

加拿大上市药品查询-加拿大药品数据库

在加拿大&#xff0c;药品的安全性、有效性和质量是受到严格监管的。根据《食品药品法案》的规定&#xff0c;所有药品制造商必须提供充分的科学证据&#xff0c;证明其产品的安全性和有效性。为此&#xff0c;加拿大卫生部建立了一个全面的药品数据库 &#xff08;DPD) &#…

Websocket自动消息回复服务端工具

点击下载《Websocket自动消息回复服务端工具》 1. 前言 在进行Websocket开发时&#xff0c;前端小伙伴通常是和后端开发人员同步进行项目开发&#xff0c;经常会遇到后端开发人员接口还没开发完&#xff0c;也没有可以调试的环境&#xff0c;只能按照接口文档进行“脑回路开发…

深入探究理解大型语言模型参数和内存需求

概述 大型语言模型 取得了显著进步。GPT-4、谷歌的 Gemini 和 Claude 3 等模型在功能和应用方面树立了新标准。这些模型不仅增强了文本生成和翻译&#xff0c;还在多模态处理方面开辟了新天地&#xff0c;将文本、图像、音频和视频输入结合起来&#xff0c;提供更全面的 AI 解…

昇思25天学习打卡营第20天|Diffusion扩散模型

Mindspore框架利用扩散模型DDPM生成高分辨率图像&#xff08;生成高保真图像项目实践&#xff09; Mindspore框架利用扩散模型DDPM生成高分辨率图像|&#xff08;一&#xff09;关于denoising diffusion probabilistic model &#xff08;DDPM&#xff09;模型Mindspore框架利…