在很久以前有寫了一篇文章 [1] 紀錄要如何用 logback 把 log 導出到 AWS CloudWatch,那時是用 logback.xml 直接設定,不過有個缺點是設定中指定在 logback.xml 裡的 Log Stream 的名字是個寫死的固定值,這在多節點的狀況可能會有問題。因為 CloudWatch 對於任一 Log Stream 同時只能允許一個執行緒存取,這會導致當應用程式會擴展成兩個以上的節點的時候,輸出 log 可能會有問題。
實務上想要解決這個問題,可能有多種解法吧,這邊紀錄的方法也不見得是最好的方法,不過起碼是可運行的選擇。
這裡的概念是~我想要讓 logback 做類似 AWS Lambda 的行為,也就是隨機產生 UUID 作為 Log Stream 的名稱,然後讓應用程式在啟動的時候自動決定。不過實際執行時,在 logback.xml 裡想這麼做總是遇到一些阻礙,可能只是我對於 CI/CD 的程序還不是那麼熟練,所以想不到合適的方法。總之最後我選擇的方式是用程式碼動態生成 Appender 的設定,並且動態加入到 Logger 中。
Maven 設定
在進行前要記得先在 Maven 加入相關的 dependency [2]:
<dependency>
<groupId>co.wrisk.logback</groupId>
<artifactId>logback-ext-cloudwatch-appender</artifactId>
<version>1.0.9</version>
</dependency>同時要稍微注意,這個函式庫使用的是 AWS SDK v1,預設使用的版本是 1.11.35。
動態加入 Logger
動態加入 Logger 的程式碼內容如下:
protected void initiateCloudWatchLogs() {
LOGGER.info("Initiating the log output for AWS CloudWatch.");
String nameOfLogStream = "application-" + UUID.randomUUID().toString();
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
PatternLayoutEncoder patternLayout = new PatternLayoutEncoder();
patternLayout.setContext(context);
patternLayout.setPattern("[%thread] %-5level %logger{35} - %msg%n%xThrowable");
patternLayout.start();
CloudWatchAppender cloudWatchAppender = new CloudWatchAppender();
cloudWatchAppender.setContext(context);
cloudWatchAppender.setRegion("us-west-2");
cloudWatchAppender.setLogGroup("/my-app/loggroup");
cloudWatchAppender.setLogStream(nameOfLogStream);
cloudWatchAppender.setCharset(StandardCharsets.UTF_8);
cloudWatchAppender.setEncoder(patternLayout);
cloudWatchAppender.start();
AsyncAppender asyncAppender = new AsyncAppender();
asyncAppender.setContext(context);
asyncAppender.addAppender(cloudWatchAppender);
asyncAppender.setQueueSize(DEFAULT_LOG_QUEUE_SIZE);
asyncAppender.setDiscardingThreshold(DEFAULT_LOG_DISCARDING_THRESHOLD);
asyncAppender.start();
ch.qos.logback.classic.Logger logbackLogger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
logbackLogger.addAppender(asyncAppender);
LOGGER.info("Log initiation is complete. The log stream is set to '{}'.", nameOfLogStream);
}上述的 Appender 設定基本上行為跟 [1] 很相似,只是多了在外層又包裝了非同步的 AsyncAppender 而已。最後實際要使用時,需要在某個適當的位置把這段 method 放進去呼叫,最好是在應用程式剛啟動的時候就呼叫它。
不過目前在實驗這個設定方式時,遇到一點奇怪的問題,有些 Logger 的輸出反應好像跟我預期的略為不同。比如說我的 Root Logger 是設定 INFO level,照理說應該所有 WARN 和 ERROR 的錯誤訊息也會輸出到 CloudWatch 才對,但實際執行時好像不會。這部份現在還沒搞清楚是哪邊弄錯了,如果之後有找到原因的話再回來補上。
沒有留言:
張貼留言