• SpringBoot2 集成日志,复杂业务下的自定义实现


    本文源码:GitHub·点这里 || GitEE·点这里

    一、日志体系集成

    1、日志管理

    在系统的开发中,最关键的一个组件工具就是日志,日志打印方便问题排查,或者生产事故回溯,日志记录用来监控并分析系统性能点,并以此为依据,不断对系统进行优化;同时基于用户的操作日志,对用户行为进行分析,开发智能推荐的功能,或者进行营销投放,这在系统中都是常见且关键的业务流程。

    2、ELK日志体系

    在大型系统架构中,ELK的日志管理系统是系统必备功能,ELK-Stack是Elasticsearch、Logstash、Kiban三个开源软件的组合,通常用来做日志分析,实时数据检索。基于Logstash做数据流动通道,使日志数据不断的流入搜索组件,基于Elasticsearch做数据实时查询,基于Kiban的ES可视化界面,以此实现日志数据的搜集、存储、分析等核心功能,且该体系方便扩展。

    ELK相关文章:

    基于ELK体系的核心操作,有关于ElasticSearch其他文章可以自行查阅之前的内容,这里不在陈列,好像很多东西都是这样一点点积累出来的。

    二、集成环境

    1、项目结构

    defined-log-api:测试工程;

    defined-log-config:日志核心模块,依赖之后使用该模块下注解即可;

    2、数据表结构

    CREATE TABLE dt_defined_log (
    	id INT ( 11 ) NOT NULL AUTO_INCREMENT COMMENT '主键',
    	class_name VARCHAR ( 200 ) DEFAULT NULL COMMENT '请求类名',
    	method_name VARCHAR ( 100 ) DEFAULT NULL COMMENT '请求方法名',
    	method_desc VARCHAR ( 100 ) DEFAULT NULL COMMENT '请求方法描述',
    	api_type INT ( 1 ) DEFAULT 0 COMMENT 'API类型',
    	biz_nature INT ( 1 ) DEFAULT 0 COMMENT '业务性质类型',
    	data_flow_type INT ( 1 ) DEFAULT 0 COMMENT '日志数据流向',
    	req_param VARCHAR ( 200 ) DEFAULT NULL COMMENT '请求报文',
    	res_param VARCHAR ( 200 ) DEFAULT NULL COMMENT '响应报文',
    	PRIMARY KEY ( `id` ) 
    ) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '日志记录表';
    

    这里完全基于业务需求自定义即可。

    三、核心代码说明

    1、注解参数

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @Documented
    public @interface DefinedLog {
    
        /**
         * 操作类型
         */
        ApiTypeEnum apiType () ;
    
        /**
         * 方法描述
         */
        String methodDesc();
    
        /**
         * 业务性质
         */
        BizNatureEnum bizNature() ;
    
        /**
         * 数据流向,与业务性质关联
         */
        DataFlowEnum dataFlow() ;
    
        /**
         * 存储入参
         */
        boolean isSaveReqParam () default true ;
    
        /**
         * 存储出参
         */
        boolean isSaveResParam() default true ;
    
        /**
         * 是否需要异步处理
         */
        boolean isAsync () default false ;
    }
    

    这里描述一下如下几个参数的意思:

    bizNature:业务性质,即该日志是否有分析,或者营销推广操作,例如在在电商业务中,浏览系列商品后是否推送广告;

    dataFlow:数据流向,即数据存储后是否向其他数据源推送,常见可能推送到MQ或者Redis或者分析引擎中,推荐类系统中对关键日志实时性要求极高,可以基于此做用户行为实时分析;

    isAsync:是否异步处理,在一些并发高的接口中,避免日志记录成为性能问题的一个因素;

    其他相关参数都是十分常见,例如接口类型增删改查,入参出参报文存储,方法模块的描述等等,这些都可以基于业务的需求自定义,然后做相关业务处理开发,思路开阔即可。

    2、切面拦截

    基于切面编程是方式,做相关日志处理,获取相应参数,构建日志模型即可。

    @Component
    @Aspect
    public class LogAop {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(LogAop.class);
    
        @Value("${spring.application.app-id}")
        private String appId ;
        @Resource
        private DefineLogService defineLogService ;
    
        /**
         * 日志切入点
         */
        @Pointcut("@annotation(com.defined.log.annotation.DefinedLog)")
        public void logPointCut() {
    
        }
    
        /**
         * 环绕切入
         */
        @Around("logPointCut()")
        public Object around (ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            Object result = null ;
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            try{
                // 执行方法
                result = proceedingJoinPoint.proceed();
                stopWatch.stop();
            } catch (Exception e){
                stopWatch.stop();
            } finally {
                // 保存日志
                LOGGER.info(" execute time: {} ms ", stopWatch.getTotalTimeMillis());
                DefineLogModel defineLogModel = buildLogParam (proceedingJoinPoint);
                defineLogModel.setResParam(JSONObject.toJSONString(result));
                defineLogService.saveLog(defineLogModel) ;
            }
            return result ;
        }
    
        private DefineLogModel buildLogParam (ProceedingJoinPoint point){
    
            DefineLogModel defineLogModel  = new DefineLogModel() ;
    
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method reqMethod = signature.getMethod();
            String className = point.getTarget().getClass().getName();
            Object[] reqParam = point.getArgs();
    
            LOGGER.info("请求方法:"+reqMethod.getName());
            LOGGER.info("请求类名:"+className);
            LOGGER.info("请求参数:"+ JSONObject.toJSONString(reqParam));
            // 获取方法上注解
            reqMethod.getAnnotation(DefinedLog.class).getClass();
            DefinedLog definedLog = reqMethod.getAnnotation(DefinedLog.class);
    
            // 构建参数
            String methodName = reqMethod.getName() ;
            Integer apiType = definedLog.apiType().getApiType();
            String apiTypeDesc = definedLog.apiType().getApiTypeDesc();
            String methodDesc = definedLog.methodDesc() ;
            Integer bizNature = definedLog.bizNature().getBizNature() ;
            Integer dataFlowType = definedLog.dataFlow().getDataFlowType();
            boolean isSaveReqParam = definedLog.isSaveReqParam();
            boolean isSaveResParam = definedLog.isSaveResParam();
            boolean isAsync = definedLog.isAsync() ;
    
            defineLogModel.setAppId(appId);
            defineLogModel.setClassName(className);
            defineLogModel.setMethodName(methodName);
            defineLogModel.setMethodDesc(methodDesc);
            defineLogModel.setApiType(apiType);
            defineLogModel.setApiTypeDesc(apiTypeDesc);
            defineLogModel.setBizNature(bizNature);
            defineLogModel.setDataFlowType(dataFlowType);
            defineLogModel.setSaveReqParam(isSaveReqParam);
            defineLogModel.setSaveResParam(isSaveResParam);
            defineLogModel.setAsync(isAsync);
            defineLogModel.setReqParam(JSONObject.toJSONString(reqParam));
    
            return defineLogModel ;
        }
    }
    

    3、使用方式

    DefinedLog注解在接口方法上即可。

    @RestController
    public class LogController {
    
        @GetMapping("/logApi")
        @DefinedLog(apiType=ApiTypeEnum.COMPOSITE,
                    methodDesc="测试日志",
                    bizNature= BizNatureEnum.DEFAULT,
                    dataFlow= DataFlowEnum.DEFAULT)
        public String logApi (@RequestParam("param") String param){
            return "success-re" ;
        }
    
    }
    

    4、记录参数

    这样自定义日志流程就完成了。

    四、源代码地址

    GitHub·地址
    https://github.com/cicadasmile/middle-ware-parent
    GitEE·地址
    https://gitee.com/cicadasmile/middle-ware-parent
    

    推荐阅读:微服务架构系列

    标题
    微服务架构:项目技术选型简介,架构图解说明
    微服务架构:业务架构设计,系统分层管理
    微服务架构:数据库选型简介,业务数据规划设计
    微服务架构:中间件集成,公共服务封装
    微服务架构:SpringCloud 基础组件应用设计
    微服务架构:通过业务、应用、技术、存储,聊聊架构
    微服务技术栈:常见注册中心组件,对比分析
    微服务技术栈:流量整形算法,服务熔断与降级
    微服务技术栈:API网关中心,落地实现方案
  • 相关阅读:
    第七十三天 how can I 坚持
    [leetcode]Climbing Stairs
    poj1204之AC自动机
    [leetcode]Sqrt(x)
    hibernate配置文件hibernate.cfg.xml的详细解释
    画板社交工具开发分享——HTML5 canvas控件、PHP、社交分享学习(一)
    我的计算几何学题目分类
    追梦
    mysql实现增量备份
    [leetcode]Plus One
  • 原文地址:https://www.cnblogs.com/cicada-smile/p/13646708.html
Copyright © 2020-2023  润新知