在很久以前有寫了一篇文章 [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 才對,但實際執行時好像不會。這部份現在還沒搞清楚是哪邊弄錯了,如果之後有找到原因的話再回來補上。
沒有留言:
張貼留言