Java日誌記錄幾種實現方案

小码A梦發表於2024-12-07

在平時使用到一些軟體中,比如某寶或者某書,透過記錄使用者的行為來構建和分析使用者的行為資料,同時也能更好最佳化產品設計和提升使用者體驗。比如在一個訂單系統中,需要確定追蹤使用者的行為,比如:

  • 登入/登出
  • 瀏覽商品
  • 加購商品
  • 搜尋商品關鍵字
  • 下單

上述行為就需要使用到日誌系統來儲存或者記錄資料,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 存在的問題,事件監聽同樣存在,報錯和耗時都會影響業務程式碼。報錯可以使用異常捕獲,延遲問題可以使用非同步方式解決,而非同步另起執行緒也順便解決了報錯影響業務程式碼的問題。
  • 訊息佇列
    • 優點: 使用於高併發日記記錄場景
    • 問題: 增加系統的複雜性和穩定性,還需要考慮訊息的丟失和重複消費問題。

相關文章