大公司都在用的-日志组件| Java Debug 笔记

1,097 阅读4分钟

**本文正在参加「Java主题月 - Java Debug笔记活动」,详情查看 活动链接 **

前言

项目初期,为了快速迭代上线,很多功能都是项目搭建者从别的地方Copy过去,然后根据项目实际情况进行相应的修改。

后期伴随着业务的迭代,对于中心化组件的需求也渐渐产生。比如我的老东家,在K8S上差不多部署了200个项目,顺理成章的诞生了授权组件,日志组件,注解组件,长连接服务等。

当然啦,我们今天的主角是日志组件。

要素

格式

日志的格式一般包含几种类型:入参还是出参,类,方法,内容,开始时间,描述,还是耗时。有了固定的格式后,日志的采集会变得相对容易点。

如果是对安全有要求的,可能还会带上加密信息和签名。

可配置

不能说你用了日志组件,然后所有的方法中都打印日志,大大浪费了硬盘空间,暴殄天物。

  1. 有些需要按项目层面打日志,例如controller层面和service层面。
  2. 有些service的方法我不需要打印日志,那我需要加个注解用来表示不用打印日志。
  3. 有些方法不需要打印入参,只需要打印出参,这时候我需要支持出入参是否打印配置。
  4. 有些方法的入参或者出参数据量较大,但数据又不关键。同时方法的开始时间和耗时又必须要保留。

看到这里肯定有人要说了,哪有这么复杂。但相信我,在金融级别的系统里就是有这种需求。

侵入性

SpringBoot诞生初期为什么会快速流行,就是源于它配置简单。 如果一个组件的代码侵入性很强,用的人就会大打折扣甚至无人问津。

在老东家搞的Dubbo组件就因为需要在apollo加一行配置,推了好久才被同事渐渐接受。太难了~~

实现

格式

这是车辙自己在用的日志格式

[类型:'入参'] | [类:'TestLogService'] | [方法:'annotest'] | [入参:'[{"yuan":"123456.789"}]'] | [开始时间:'2021-05-13 17:49:46'] | [描述:'测试'][类型:'出参'] | [类:'TestLogService'] | [方法:'annotest'] | [出参:'null'] | [结束时间:'2021-05-13 17:49:46'] | [耗时:31] | [描述:'测试']

可配置

  1. 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import(EnableLogRegister.class)
public @interface EnableApplicationLog {
    /**
     * 是否开启controller注解
     * @return
     */
    boolean enableController() default true;
    /**
     * 是否开启service注解
     * @return
     */
    boolean enableService() default true;
}
  1. 通过实现ImportBeanDefinitionRegistrar,将自己需要的类交给Spring管理。这样就根据EnableApplicationLog 注解上的配置判断是否让切面类被Spring管理。因此组件没有采用SpringBoot Starter的形式,相对灵活。可以参考Mybatis的MapperScan是怎么做的。
class EnableLogRegister implements ImportBeanDefinitionRegistrar{
    boolean enableController = annoAttrs.getBoolean("enableController");
    if(enableController){
        basePackages.add(BASE_HANDLER_CONTROLLER);
    }
    boolean enableService = annoAttrs.getBoolean("enableService");
    if(enableService){
        basePackages.add(BASE_HANDLER_SERVICE);
    }
}
  1. 定义切点
@Aspect
@Slf4j
public class EnableLogControllerLogHandler extends BaseLogHandler {
    @Pointcut("within(@(org.springframework.stereotype.Controller || org.springframework.web.bind.annotation.RestController) *)")
    private void allMethod() {
    }

    @Around("allMethod()")
    public Object doAround(ProceedingJoinPoint call) throws Throwable {
        return handle(call);
    }
}
// service层面切点与controller一致
  1. 定义可配置功能注解。
// 不需要打印日志注解
public @interface NoNeedLog {
}
// 需要打印日志的配置注解
public @interface LogParam {

    String desc() default "";

    boolean isPrintIn() default true;

    boolean isPrintOut() default true;
}
  1. 顶层handler处理
        // 判断是否需要打印日志
        if(!needLog(call)){
            Object result = call.proceed();
            return result;
        }

        Boolean isPrintIn = true, isPrintOut = true;
        String desc = null;
        // 获取是否需要打印入参和出参
        LogParam logParam = this.getLogParam(call);
        if(logParam != null){
            isPrintIn = logParam.isPrintIn();
            isPrintOut = logParam.isPrintOut();
            desc = logParam.desc();
        }

        // 入参参数打印
        Long startStamp = System.currentTimeMillis();
        if(isPrintIn){
            doBeforeParam(call, desc);
        }

        // invoker调用
        Object result = call.proceed();

        // 返回参数打印
        if(isPrintOut){
            doAfterParam(result, startStamp, call, desc);
        }
        return result;

上面的代码基本上包含了主要的实现,相信小伙伴们应该对此有了一定的认识,可以在下方评论中留言哟!

使用

SpringBoot启动类上添加注解开启日志打印

@EnableApplicationLog(enableController = false)
public class TrackAdapterApplication {}

方法不需要日志

@NoNeedLog
public void annotest(AnnoBean annoBean)

日志配置不需要入参,添加描述

@LogParam(desc = "测试", isPrintIn = false)
public void annotest(AnnoBean annoBean)

结尾

其实日志组件里还有不少东西可以写,比如说抛出的异常我怎么处理。捕获打印异常并继续抛出还是直接做处理并返回,亦或是做一个异常处理组件用来记录并告警。这边也不能给出一个完美的答案,毕竟适合的才是最好的。

这是时隔多月的第一篇博文,接下来希望出一些更高质量的文章,咱们下期见啦!