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 並發送通知

我將介紹如何使用 DiscordLine NotifyPrometheus 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