將 Spring Boot Log 透過 Logstash 導入 Elasticsearch

在 Spring Boot 應用程式中,Log 通常會先輸出到 Console 或檔案,方便開發與單機排查問題。不過當服務數量變多、部署環境變複雜後,單靠各主機上的 Log 檔案會讓查詢與追蹤變得不方便。這時可以透過 Logstash 接收 Spring Boot 輸出的結構化 Log,再寫入 Elasticsearch,讓後續查詢、分析與視覺化更加集中。

本文會記錄如何在 Linux 環境安裝 Logstash,設定 Logstash 將 TCP 收到的 JSON Log 寫入 Elasticsearch,最後再於 Spring Boot 的 logback 設定中加入 Logstash Appender,讓應用程式 Log 可以送進 Elasticsearch。


安裝必要套件與匯入 PGP 金鑰

首先更新本地套件清單,並安裝新增 APT 儲存庫時會用到的必要工具。接著下載 Elastic 官方 PGP 金鑰,並轉成 keyring 格式存放在系統路徑中,供後續套件來源驗證使用。

1
2
3
4
5
6
# 更新本地套件清單並安裝必備工具
sudo apt-get update
sudo apt-get install apt-transport-https gnupg wget -y

# 下載 Elasticsearch PGP 金鑰並存為 keyring
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg

加入 APT 儲存庫

接著將 Elastic 9.x 的 APT 儲存庫加入系統來源清單。這裡會指定前面匯入的 keyring,讓 apt 在安裝套件時能驗證來源。

1
echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/9.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-9.x.list

安裝 Logstash

儲存庫加入完成後,再次更新套件清單,接著安裝 Logstash。

1
2
sudo apt-get update
sudo apt-get install logstash -y

啟動 Logstash 服務

套件安裝完成後,先重新載入 systemd 設定,然後將 Logstash 設定為開機自動啟動,並立即啟動服務。若需要觀察 Logstash 啟動狀態,可以使用 journalctl 查看服務日誌。

1
2
3
4
5
6
sudo systemctl daemon-reload

sudo systemctl enable logstash.service
sudo systemctl start logstash.service

sudo journalctl -u logstash.service -f

設定 Logstash conf

Logstash 啟動後,接著設定 pipeline。這份設定會透過 TCP 監聽 5044 port,使用 json_lines codec 接收 Spring Boot 傳來的 JSON Log。

在 filter 階段,若 Log 事件中沒有 application 欄位,會補上 unknown-app;若有提供 application,則會轉成小寫,並將不符合索引名稱使用習慣的字元替換成 -。最後 output 會將資料寫入 Elasticsearch,並依照 application 與日期產生索引名稱。

本文範例將 Logstash pipeline 設定放在 /etc/logstash/conf.d/app-logstash.conf。建立或編輯這個檔案後,將下方設定內容寫入其中。

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
33
34
35
36
37
38
input {
tcp {
host => "0.0.0.0"
port => 5044
codec => json_lines
}
}

filter {
if ![application] or [application] == "" {
mutate {
add_field => { "application" => "unknown-app" }
}
}

mutate {
lowercase => ["application"]
gsub => [
"application", "[^a-z0-9._-]", "-"
]
}
}

output {
elasticsearch {
hosts => ["https://127.0.0.1:9200"]
user => "${ES_USER}"
password => "${ES_PASSWORD}"
ssl_enabled => true
ssl_certificate_authorities => ["/etc/logstash/certs/http_ca.crt"]
ssl_verification_mode => "full"
index => "%{[application]}-%{+YYYY.MM.dd}"
}

stdout {
codec => rubydebug
}
}

如果 Logstash 連線 Elasticsearch 時出現憑證錯誤,可以將 Elasticsearch 的 CA 憑證複製到 Logstash 可讀取的位置,並調整目錄權限。

這個錯誤通常是因為 Elasticsearch 啟用 HTTPS 後,Logstash 需要透過 CA 憑證驗證 Elasticsearch 的 TLS 憑證。如果 ssl_certificate_authorities 指定的憑證不存在,或是 Logstash 服務帳號沒有讀取權限,就會在啟動 pipeline 或連線 Elasticsearch 時發生憑證驗證失敗。

以下指令會先建立 Logstash 專用的憑證目錄,接著把 Elasticsearch 安裝時產生的 http_ca.crt 複製到該目錄,最後將目錄擁有者調整為 logstash,確保 Logstash 服務可以正常讀取憑證。

1
2
3
4
5
6
7
8
# 建立 Logstash 存放 Elasticsearch CA 憑證的目錄
sudo mkdir -p /etc/logstash/certs

# 將 Elasticsearch 產生的 CA 憑證複製到 Logstash 可讀取的位置
sudo cp /etc/elasticsearch/certs/http_ca.crt /etc/logstash/certs/

# 調整憑證目錄擁有者,讓 logstash 服務帳號具備讀取權限
sudo chown -R logstash:logstash /etc/logstash/certs

Logstash 正常啟動後,可以在服務日誌中看到 pipeline 已經開始執行,並且 TCP input listener 成功綁定到 0.0.0.0:5044。畫面中的 Pipeline startedStarting tcp input listener,以及 Pipelines running {:count=>1, :running_pipelines=>[:main]} 都代表主要 pipeline 已經載入並處於執行中狀態。此時 Spring Boot 應用程式只要將 Log 傳送到 Logstash 主機的 5044 port,Logstash 就可以接收事件並依照前面的 output 設定寫入 Elasticsearch。

設定 Spring Boot Log

Logstash 端設定完成後,接著在 Spring Boot 的 logback 設定中加入 Logstash Appender。這裡使用 LogstashTcpSocketAppender,透過 ${LOGSTASH_DESTINATION} 指定 Logstash 位址,並在 encoder 中加入自訂欄位,讓每筆 Log 都帶有應用程式名稱與環境資訊。

1
2
3
4
5
6
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>${LOGSTASH_DESTINATION}</destination>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"application":"${APP_NAME}","environment":"dev"}</customFields>
</encoder>
</appender>

接著將 LOGSTASH appender 掛到 root logger。這樣 Spring Boot 在輸出 Console 與 File Log 的同時,也會把同一份 Log 傳送到 Logstash。

1
2
3
4
5
<root level="${ROOT_LOG_LEVEL}">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
<appender-ref ref="LOGSTASH" />
</root>

完成以上設定後,Spring Boot 的 Log 會先透過 TCP 傳送到 Logstash,再由 Logstash 寫入 Elasticsearch。後續只要依照 application-YYYY.MM.dd 的索引格式查詢,就可以針對不同應用程式與日期快速篩選 Log。