Contents

在 Ubuntu 上監控系統錯誤層級 Log 並發送 Discord 通知腳本

最近我在尋找一種方法,讓樹梅派能夠監控系統 log,並在出現錯誤層級 Log 時發送通知。我曾考慮過 Prometheus Alertmanager,但它似乎無法查看 Log。我也看過 Loki,它確實能達到我要的效果,但我不希望建立集中式 Log,因為樹梅派的儲存空間有限。我也考慮過 mtail,但它可能無法正確解析 Log 層級,所以我最終沒有選擇它。至於 openITCOCKPIT,雖然它需要安裝資料庫,但我認為這不適合在樹梅派上進行,儘管官方有提供安裝教學。

為何我需要系統錯誤通知?

https://gist.github.com/assets/75846914/2e85b1f2-9a97-4b34-8497-58d51f81553a

我曾在 Facebook 社群中看到有人在收到 SELinux 系統入侵的通知後,能夠立即採取行動。這篇留言提到他們是使用 Cockpit,但我發現這個工具並沒有我需要的功能。我知道 Zabbix 也是一個選擇,但我這次不打算使用它。

我注意到 Cockpit 是使用 journalctl 來查看系統 log。由於沒有現成的工具可以滿足我的需求,我決定自己開發一個😆。

準備工作

首先,我們需要安裝 jq。你可以使用以下的命令來安裝:

1
2
sudo apt update 
sudo apt install jq

監聽 Log 並發送通知

我將介紹如何使用 Discord、Line Notify 和 Prometheus Alertmanager 來監聽 Log 並發送通知。你可以根據自己的需求選擇使用哪一種方法。

使用 Discord Webhook 進行通知

Warning
請確保你已經調整了對應的 Webhook Url

以下是一個 Bash 腳本,它會監聽系統的 Log,並在出現錯誤時通過 Discord Webhook 發送通知。你需要將 WEBHOOK_URL 變數設定為你的 Discord Webhook Url。此腳本會每小時檢查一次 Log,並在出現錯誤時發送通知。

 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
39
40
#!/bin/bash

# Discord webhook URL
WEBHOOK_URL="https://discord.com/api/webhooks/...."
# Show Log Line Count
SHOW_LOG_CNT=10
# Set the priority level (0-7)
PRIORITY=3

# Get the hostname and IP address
HOSTNAME=$(hostname)
IP_ADDRESS=$(hostname -I | awk '{print $1}')

# Run the journalctl command with the specified priority
ALL_OUTPUT=$(journalctl --since=-1hours --priority=$PRIORITY --no-pager |  grep -v -- "-- No entries --" | grep -v -- "-- Logs begin at")

# Count the number of error messages
ERROR_COUNT=$(echo "$ALL_OUTPUT" | wc -l)

# Extract the first SHOW_LOG_CNT lines of output
OUTPUT=$(echo "$ALL_OUTPUT" | head -n $SHOW_LOG_CNT)

# Set the color based on the priority level
COLOR=""
case $PRIORITY in
  0|1|2|3) COLOR="16711680" ;; # Red for emerg, alert, crit, err
  4) COLOR="16776960" ;; # Yellow for warning
  5|6) COLOR="65280" ;; # Green for notice, info
  7) COLOR="255" ;; # Blue for debug
esac

# Check if there are any error messages
if [ -n "$OUTPUT" ]; then
  # Format the message as a JSON payload with embeds
  JSON_PAYLOAD=$(jq -n --arg msg "$OUTPUT" --arg hn "$HOSTNAME" --arg ip "$IP_ADDRESS" --argjson ec "$ERROR_COUNT" --argjson color "$COLOR" \
    '{content: $msg, embeds: [{title: "Error Report", color: $color, fields: [{name: "Hostname", value: $hn, inline: true}, {name: "IP Address", value: $ip, inline: true}, {name: "Number of Errors", value: $ec, inline: true}]}]}')

  # Send the message to the Discord webhook
  curl -X POST -H "Content-Type: application/json" -d "$JSON_PAYLOAD" $WEBHOOK_URL
fi

使用 Line Notify 進行通知

如果你想使用 Line Notify 進行通知,你需要設定 LINE_TOKEN。你可以參考我之前寫的這篇 Line Notify 教學設定。

以下是一個 Bash 腳本,它會監聽系統的 Log,並在出現錯誤時通過 Line Notify 發送通知。你需要將 LINE_TOKEN 變數設定為你的 Line Notify Token。此腳本會每小時檢查一次 Log,並在出現錯誤時發送通知。

 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
#!/bin/bash

# Line Notify token
LINE_TOKEN="YOUR_LINE_NOTIFY_TOKEN"

# Show Log Line Count
SHOW_LOG_CNT=10
# Set the priority level (0-7)
PRIORITY=3

# Get the hostname and IP address
HOSTNAME=$(hostname)
IP_ADDRESS=$(hostname -I | awk '{print $1}')

# Run the journalctl command with the specified priority
OUTPUT=$(journalctl --since=-1hours --priority=$PRIORITY --no-pager -n $SHOW_LOG_CNT)

# Count the number of error messages
ERROR_COUNT=$(echo "$OUTPUT" | wc -l)

# Check if there are any error messages
if [ -n "$OUTPUT" ]; then
  # Format the message
  MESSAGE="Hostname: $HOSTNAME\nIP Address: $IP_ADDRESS\nNumber of Errors: $ERROR_COUNT\n$OUTPUT"

  # Send the message to the Line Notify API
  curl -X POST -H "Authorization: Bearer $LINE_TOKEN" -F "message=$MESSAGE" https://notify-api.line.me/api/notify
fi

使用 Prometheus AlertManager 進行通知

Warning
請確保你已經設定了 Prometheus AlertManager 的網址。

以下是一個 Bash 腳本,它會監聽系統的 Log,並在出現錯誤時通過 Prometheus AlertManager 發送通知。你需要將 ALERTMANAGER_API 變數設定為你的 Prometheus AlertManager API 網址。此腳本會每小時檢查一次 Log,並在出現錯誤時發送通知。

 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
#!/bin/bash

# AlertManager API URL
ALERTMANAGER_API="http://192.168.xx.xx:9093/api/v1/alerts"

# Set the priority level (0-7)
PRIORITY=3

# Get the hostname and IP address
HOSTNAME=$(hostname)
IP_ADDRESS=$(hostname -I | awk '{print $1}')
INSTANCE="$HOSTNAME($IP_ADDRESS)"

# Run the journalctl command with the specified priority
ALL_OUTPUT=$(journalctl --since=-1hours --priority=$PRIORITY --no-pager |  grep -v -- "-- No entries --" | grep -v -- "-- Logs begin at")

# Count the number of error messages
ERROR_COUNT=$(echo "$ALL_OUTPUT" | wc -l)

# Extract the first SHOW_LOG_CNT lines of output
OUTPUT=$(echo "$ALL_OUTPUT" | head -n $SHOW_LOG_CNT)

# Format the summary message
SUMMARY="總共 $ERROR_COUNT 個錯誤訊息"

# Check if there are any error messages
if [ -n "$OUTPUT" ]; then
  # Format the alert as a JSON payload
  JSON_PAYLOAD=$(jq -n --arg inst "$INSTANCE" --arg sum "$SUMMARY" --arg msg "$OUTPUT" \
          '[{labels: {alertname: "JournalctlError", instance: $inst }, annotations: {  description: $msg, summary: $sum}}]')

  # Send the alert to the AlertManager API
  curl -X POST -H "Content-Type: application/json" -d "$JSON_PAYLOAD" $ALERTMANAGER_API
fi

排程設定

在 Linux 系統中,我們可以使用 crontab 來設定定時任務。以下是如何設定每小時執行一次腳本的步驟:

  1. 首先,打開終端機並輸入 crontab -e 來編輯你的 crontab 文件。

  2. 在文件中,你可以添加或修改任務。每一行都代表一個任務,並且由六個字段組成:分、時、日、月、週和命令。例如,0 * * * * /home/username/scripts/hourly_error_report.sh > /dev/null 2>&1 這行命令的意思是每小時的第 0 分鐘執行 /home/username/scripts/hourly_error_report.sh 這個腳本。

Info

bash ... > /dev/null 2>&1 這段程式碼的作用是將指令的輸出和錯誤輸出都重定向到/dev/null,這樣就可以將它們丟棄,不會在終端上顯示或存儲。這在某些情況下很有用,例如當你只關心指令是否成功執行,而不關心輸出或錯誤訊息時。

這段程式碼的每個部分:

  • >:這是一個重定向運算符,用於將指令的輸出重定向到指定的位置。
  • /dev/null:這是一個特殊的設備文件,它可以被視為一個黑洞。將輸出重定向到/dev/null意味著將輸出丟棄,不會在終端上顯示或存儲。
  • 2>&1:這是一個錯誤輸出重定向的語法。數字2代表錯誤輸出,而&1代表標準輸出。這個語法的意思是將錯誤輸出重定向到與標準輸出相同的位置,也就是/dev/null。
  1. 最後,儲存並關閉文件。你的任務將會在設定的時間自動執行。

以下是具體的命令:

1
2
3
4
crontab -e

# 每小時執行
0 * * * * /home/username/scripts/hourly_error_report.sh > /dev/null 2>&1

彩蛋

monit