Java 日志记录几种实现方案 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
jeremylai
V2EX    程序员

Java 日志记录几种实现方案

  •  1
     
  •   jeremylai 319 天前 1397 次点击
    这是一个创建于 319 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在平时使用到一些软件中,比如某宝或者某书,通过记录用户的行为来构建和分析用户的行为数据,同时也能更好优化产品设计和提升用户体验。比如在一个订单系统中,需要确定追踪用户的行为,比如:

    • 登录/登出
    • 浏览商品
    • 加购商品
    • 搜索商品关键字
    • 下单

    上述行为就需要使用到日志系统来存储或者记录数据,Java 有几种日志方案,简单介绍几种实现方案,以及需要注意的点。

    日志添加需要注意问题

    根据业务的不同,需要使用匹配到合适的日志方案。也需要注意几个问题:

    • 不能影响原来的业务逻辑。
    • 不能报错,即使报错,不能影响原有的业务代码。
    • 对于耗时的日志代码,使用异步方法
    • 侵入性低,尽量少改动原始代码

    Spring AOP

    Spring AOP 通过切面编程实现不修改原有代码,而动态添加功能的能力。这种方式有以下几个好处:

    • 侵入性低
    • 可重用性强

    本文使用基于注解的 AOP,首先定义一个切面注解 AopTest:

    /** * 切面注解 */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface AopTest { } 

    再创建通知 @Around

    @Around("@annotation(com.test.annotation.AopTest)") public Object annotationTest(ProceedingJoinPoint joinPoint) throws Throwable { log.info("执行前"); Object result = joinPoint.proceed(); // 执行目标方法 Object[] args = joinPoint.getArgs(); log.info("执行后"); return result; } 

    一般都会在执行后添加日志即可,想要在那个方法或者接口加日志,只需要在方法上添加注解即可,比如在接口添加注解:

    @GetMapping @AopTest public String first(String param) { log.info("执行 first 方法"); return "result " + param; } 

    请求接口后,就有如下的输出:

    执行前 执行 first 方法 执行后 

    但是切面有一个问题,执行切面报错,方法也无法执行,就需要捕获异常,保证业务代码正常执行,改造一下上面的通知:

    @Around("@annotation(com.test.annotation.AopTest)") public Object annotationTest(ProceedingJoinPoint joinPoint) throws Throwable {log.info("执行前"); log.info("执行前"); Object result = joinPoint.proceed(); // 执行目标方法 try { Object[] args = joinPoint.getArgs(); // 添加日志 log.info("执行后"); int a = 1/0; } catch (Exception e) { log.error(e.getMessage()); } return result; } 

    改造后,即使切面报错,也不会影响业务代码的执行了。

    AOP 是同步执行的,如果日志添加是一个比较耗时的操作,也会影响接口的响应速度,此时可以使用异步的方式,比如消息队列。

    总结一下,Spring AOP 有以下几个优点和缺点。

    • 优点:

      • 侵入性低
      • 可重用性强

    缺点及解决方案:

    1 、切面报错可能会影响业务代码

    • 问题:在切面的异常如果没有正确处理,可能会影响业务代码的正常执行。
    • 解决方案:
      • 捕获异常,确保业务代码正常执行
      • 一般使用 try catch 捕获异常,防止向上传播

    2 、同步执行会影响接口响应速度

    • 问题:如果切面中存在耗时的操作,同步操作会导致接口的响应速度变慢。
    • 解决方案:
      • 通过消息队列将耗时操作异步执行
      • 使用 @Async 将方法异步执行,将任务从主线程脱离出来,交给其他线程池执行

    事件监听 + 异步

    Spring 事件监听机制是一种发布-订阅模式,将事件的发布和监听解耦开来。通过这种机制,事件的发布者无需关注监听的逻辑,监听者也无需直接依赖发布者。

    Spring 事件监听有三个部分组成:

    1. 事件

    自定义一个事件,继承 ApplicationEvent:

    @Getter @Setter public class DemoEvent extends ApplicationEvent { private String name; public DemoEvent(Object source) { super(source); } } 
    1. 事件监听

    基于 @EventListener 注解监听事件:

    @Component @Slf4j public class DemoEventListener { @EventListener public void handleEvent(DemoEvent event){ log.info(event.getName()); log.info("事件监听"); } } 
    1. 事件发布

    使用 applicationEventPublisher.publishEvent 方法发布事件,

     @Autowired private ApplicationEventPublisher applicationEventPublisher; @Override public void publish() { // 执行业务代码 log.info("执行登录,当前时间 {}",LocalTime.now()); DemoEvent event = new DemoEvent(this); event.setName("hello"); applicationEventPublisher.publishEvent(event); log.info("完成登录,当前时间 {}",LocalTime.now()); log.info("执行 service"); } 

    配置好事件的发布和监听之后,在业务代码添加事件的发布,监听方法内添加日志的记录。

    Spring 事件监听虽然解耦了发布和监听,只是解耦逻辑代码,两者还是同步执行,并且都是在同一个线程执行的,所以事件监听也无法解决添加日志报错,以及耗时的问题。

    1. 验证监听方法是否同步

    在事件监听添加延迟:

     @EventListener public void handleEvent(DemoEvent event){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } log.info(event.getName()); log.info("事件监听"); } 

    控制台输出:

    执行登录,当前时间 16:52:30.799 hello 事件监听 完成登录,当前时间 16:52:33.799 执行 service 

    接口执行了 3 秒多,并且 publish 方法要等待监听方法执行完毕之后才能执行,说明事件监听是同步的

    1. 验证监听方法报错是否会影响主流程

    在监听方法添加错误代码:

    @EventListener public void handleEvent(DemoEvent event){ int a = 1/0; log.info(event.getName()); log.info("事件监听"); } 

    控制台输出:

    执行登录,当前时间 17:10:08.396 java.lang.ArithmeticException: / by zero 

    监听方法报错,接口也报错,业务代码无法执行,说明监听方法报错会影响事件发布方法

    解决报错的问题,使用异常捕获即可。而延迟的问题,就需要使用到异步的操作,异步就是另启一个线程执行监听方法,异步除了能解决延迟的问题,也顺便解决了报错的问题。

    实现异步在监听方法上添加 @Async 异步注解,监听方法添加延迟和错误代码:

    执行登录,当前时间 17:21:50.971 完成登录,当前时间 17:21:50.974 执行 service 

    publish 方法既不会延迟,也不会因为监听报错影响执行,异步完美解决耗时和报错的问题

    消息队列

    海量日志场景,消息队列是一个很好的选择,它也是解耦了发布者和订阅者,如果订阅者开启了手动确认,消费者也需要使用 try catch 捕获异常信息,确保消息能正常消费。

    总结

    本文介绍几种日志记录的实现方案:

    • Spring AOP:
      • 优点: 侵入性低,代码可重复性强
      • 问题以及解决方案:
        • 切面中可能会报错,报错会影响业务代码的正常执行,解决方法是使用 try catch 捕获异常。
        • 日志记录会影响业务代码执行效率,可以使用消息队列异步执行日志操作
    • 事件监听 + 异步
      • 优点: 解耦业务逻辑和日志记录,提升代码的内聚性。
      • 缺点以及解决方案:
        • AOP 存在的问题,事件监听同样存在,报错和耗时都会影响业务代码。报错可以使用异常捕获,延迟问题可以使用异步方式解决,而异步另起线程也顺便解决了报错影响业务代码的问题。
    • 消息队列
      • 优点: 使用于高并发日记记录场景
      • 问题: 增加系统的复杂性和稳定性,还需要考虑消息的丢失和重复消费问题。

    目前尚无回复
    关于     帮助文档     自助推广系统     博客   API     FAQ     Solana     5244 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 01:22 PVG 09:22 LAX 17:22 JFK 20:22
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86