在很久以前有寫了一篇文章 [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]:
1 2 3 4 5 |
< 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 的程式碼內容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
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 才對,但實際執行時好像不會。這部份現在還沒搞清楚是哪邊弄錯了,如果之後有找到原因的話再回來補上。
沒有留言:
張貼留言