apollo动态配置实现接口日志打印

微信公众号:橘松Java技术窝
文章首发掘金平台,后续同步更新公众号,关注后回复 “加群” 可加入互联网技术交流&内推群,和一群大厂大佬讨论面试问题。回复 “666” 可获取一线互联网具备所有资料包(包括开发软件、开发规范、经典电子pdf、以及一些精品学习课程)。

前言

想必大家在开发中都会碰到过这么几种情况,接口日志很难查啊,服务器日志太多看的眼花缭乱。这还好我可以通过前面讲的熟悉这些Java排查工具就够了一些工具完成,关键是服务器日志堆积告警,尤其是qa环境(quality assurance), 然后你收到这条短信报警连忙登录到机器,开始了一顿rm操作解决了美滋滋,过了半小时,你又收到一条短信告警,很熟悉,又是这个…

尼玛,又来了 (1).gif

于是你开始寻思这哪里天天来的这么多日志打印,出于好奇的你打开了温顺的IDEA,看着满屏的代码,时不时轻微右键看看git master 提交者,知道真相的你都想砸电脑了。

循环体里一堆日志,日志相似度很高,每个接口的参数和结果都打日志,debug日志没删除,居然还有带有’xxxTest’字样日志,从头到尾没看到一条Warn日志,除了Info日志就是Error日志,实在看不下去了要合电脑了,突然看到某个类以xxxAspect结尾的文件?哦?难道这是业务切面? 出于好奇点进去一看,尼玛 日志切面,切入点是所有biz业务代码方法…

你牛逼.gif


那么,针对日志不规范,服务器日志太多,日志治理这块我们有什么解决方案呢,于是灵机一动,万物且可配置,是不是这个东西可以做成配置的呢,于是你就开始着手设计了起来,

这么多类,这么多方法,我是不是每个接口的日志打印如何能做成开关控制就好了?搞定!

开关控制做好了,那么打印日志的内容呢,我需要打印哪些内容合适呢? 入参?返回值?

日志打印的内容为接口的入参、响应结果,响应耗时..可扩展参数等等。搞定!

日志打印内容定好了,那么日志打印级别呢?每个方法每个场景可能不一样啊,不同环境我也想不一样怎么办呢?

懂了,不是万物皆配置么,我做成配置不就好啦,配置几种不同的策略即可,搞定!

*配置?我用什么做配置呢,我这个项目是分布式项目,于是经过balabala一顿百度 你可能看到有可选的diamondapollodisconf 等,于是你丢骰子最后选择了apollo

其实都可以哈哈哈,理解理解

通过控制接口的黑白名单日志打印实现动态日志打印,搞定!

好了,思路有了,开始coding尝试实现起来

apollo动态配置实现接口日志打印

首先,我们定义一个控制开关策略的枚举LogSwitch

/**
 * @创建人 : 掘金账号 "橘松Java"
 * @创建时间 2021/7/9
 * @描述 : 需要源文件加QQ群[572411121]免费索取
 */
public enum LogSwitch {
    /**
     * 完全关闭
     */
    OFF(1,"off"),
    /**
     * 部分开启
     */
    PART_ON(2,"part on"),
    /**
     * 部分关闭
     */
    PART_OFF(3,"part off"),
    /**
     * 完全开启
     */
    ON(4,"on");
    
    LogSwitch(int code ,String name){
        this.code = code;
        this.name = name;
    }

    public static LogSwitch of(int code) {
        for(LogSwitch logSwitch : LogSwitch.values()) {
            if(logSwitch.getCode() == code) {
                return logSwitch;
            }
        }
        return null;
    }

    @Getter
    @Setter
    private int code;

    @Getter
    @Setter
    private String name;
}
复制代码

其次,日志打印的内容我们也定义一个枚举LogDimension

/**
 * @创建人 : 掘金账号 "橘松Java"
 * @创建时间 2021/7/9
 * @描述 : 需要源文件加QQ群[572411121]免费索取
 */
public enum  LogDimension {

    NONE(1,"none"),
    TIME_ELAPSE(2,"time elapse"),
    PARAM(3,"param"),
    RESULT(4,"result"),
    ALL(5,"all");
    
    LogDimension(int code,String name) {
        this.code = code;
        this.name = name;
    }

    public static LogDimension of(int code) {
        for(LogDimension logDimension : values()) {
            if(logDimension.getCode() == code) {
                return logDimension;
            }
        }
        return null;
    }

    @Getter
    @Setter
    private int code;

    @Getter
    @Setter
    private String name;
}
复制代码

好了,接下来打印的日志关键要素已经形成,我们需要定义一个切面类来完成。

既然有切面,那我们就可以一个切入点注解来完成我们的切面,定义日志打印注解LogAnnotation
不需要任何属性,对我们来说只是一个切面标识。

/**
 * @创建人 : 掘金账号 "橘松Java"
 * @创建时间 2021/7/9
 * @描述 : 需要源文件加QQ群[572411121]免费索取
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
@Documented
public @interface LogAnnotation {

}
复制代码

接下来可以写我们的切面类了,我们定义LogAspect

/**
 * @创建人 : 掘金账号 "橘松Java"
 * @创建时间 2021/7/9
 * @描述 : 需要源文件加QQ群[572411121]免费索取
 */
@Component
@Aspect
public class LogAspect {

    /**
     * 日志开关级别
     * 1.OFF 2.PART_ON 3.PART_OFF 4.ON
     * @see LogSwitch
     */
    @Value("${log_aspect_switch_level:1}")
    private int logSwitch;

    /**
     * 日志内容控制
     * 1.NONE 2.TIME_ELAPSE 3.PARAM 4.RESULT
     * @see LogDimension
     */
    @Value("${log_aspect_dimension_level:1}")
    private int logDimension;

    /**
     * 当logSwitch为2时 需处理的方法签名 逗号分隔
     * eg. "com.orange.application.xxxClient#getXXX,com.orange.application.xxxClient#getYYY"
     */
    @Value("{log_aspect_part_on_method}")
    private String partOnMethods;

    /**
     * 当logSwitch为3时 需排除的方法签名 逗号分隔
     * eg. "com.orange.application.xxxClient#getXXX,com.orange.application.xxxClient#getYYY"
     */
    @Value("${log_aspect_part_off_method}")
    private String partOffMethods;
    
    
    
    @Around("@annotation(logAnnotation)")
    //采取环绕通知
    public Object processLog (ProceedingJoinPoint joinPoint,LogAnnotation logAnnotation) throws Exception {
        //获取方法签名服务
        String serviceName = joinPoint.getSignature().getDeclaringTypeName();
        //获取方法签名方法名
        String methodName = joinPoint.getSignature().getName();
        String methodSignature = serviceName + "#" + methodName;
        //判断检查开关是否命中
        Boolean isHit = judgeIsHit(methodSignature);
        Object obj = null;
        //定义日志输出
        List<String> logArray = Lists.newArrayList();
        try {
            if(!isHit) {
                //如果没命中,即不需要打印,则执行逻辑
                obj = joinPoint.proceed();
            }else{
                //根据枚举层级 输出对应的log详情 具体日志格式化省略...
                logArray.add(formatParam(joinPoint.getArgs()));
                Stopwatch stopwatch =Stopwatch.createStarted();
                obj = joinPoint.proceed();
                stopwatch.stop();
                //统计日志耗时 添加到logArray详情 具体日志格式化省略...
                logArray.add(formatTimeElapse(stopwatch.toString()));
                //统计响应返回值 添加到logArray详情 具体日志格式化省略...
                logArray.add(formatResult(obj.toString()));
            }
        }catch (Exception e) {
            //处理业务异常
            dealBizException(e,joinPoint);
        } catch (Throwable throwable) {
            dealException(joinPoint);
        } finally {
            if(isHit){
                //如果命中,则打印日志
                log.info(formatLog(methodSignature,logArray));
            }
        }
        return obj;
    }
    
    /**
    *  判断方法签名是否命中
    */
    private Boolean judgeIsHit(String methodSignature) {
        if(LogSwitch.of(logSwitch) == null || LogDimension.of(logDimension) == null) {
            // sign log.... 没配置 
            return false;
        }
        if(LogSwitch.ON.getCode() == logSwitch){
            //配置的全部打开
            return true;
        }
        if(LogSwitch.PART_ON.getCode() == logSwitch) {
            //配置的部分打开,命中partOnMethods
            return !StringUtils.isEmpty(partOnMethods) && partOnMethods.contains(methodSignature);
        }
        if(LogSwitch.PART_OFF.getCode() == logSwitch) {
            //配置的部分打开,命中partOffMethods
            return !StringUtils.isEmpty(partOffMethods) && !partOffMethods.contains(methodSignature);
        }
        return false;
    }  
 
复制代码

总结一下

动态日志打印由相应的Aspect进行日志统一输出主要解决的是日志输出逻辑固定日志输出过多排查问题无法动态调整日志内容。结合apollo以及注解 可动态调节关注点可动态控制日志输出内容 的统一切面工具。使用方式也特别简单,在代码内需要被LogAspect动态管理日志的方法加上@LogAnnotation注解即可。

上面一切完成,那么今后你再也不用担心查看接口日志找不到了,你可以按照你想要的配置一下就可以啦!

对更多的关于 封装、通用组件、通用代码 方面感兴趣的同学可关注主页 通用组件封装专栏 后续将会分享更多的这块知识。

原创不易,喜欢博主的同学点点赞啦、 关注关注啦,好了,下期见 。

最后

  • 文章均原创,原创不易,感谢掘金平台,觉得有收获,帮忙三连哈,感谢
  • 微信搜索公众号:橘松Java技术窝,交个朋友,进互联网技术交流群
  • 文章涉及的所有代码、时序图、架构图均共享,可通过公众号加群免费索要
  • 文章若有错误,欢迎评论留言指出,也欢迎转载,麻烦标注下出处就好
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享