Graylog 異常日誌自動分析工具:從 Graylog 撈取交換器等網路設備的異常 log,由 Python 精確統計後交給 LLM 撰寫原因與處置建議,輸出繁體中文日報表(文字 / PDF / Email / LINE)。
- 完整撈取:逐小時分頁查詢 Graylog,不受單次 1000 筆上限影響;可指定任意日期補做日報
- Python 負責數字、LLM 負責解讀:IP、介面名稱、筆數、Flapping 次數一律由程式統計,LLM 只寫原因與建議
- 多種 LLM 後端:OpenAI 相容 API(vLLM、LiteLLM)或 Ollama,以設定檔切換
- 去識別化(mask):使用外部 LLM 時,IP / 主機名稱 / 帳號 / 系統名稱先換成代號才送出,報告回來後在本機還原(詳見下方)
- 程式判定優先等級:依 Flapping 次數、介面事件數、硬體關鍵字等規則分 P0 / P1 / P2,結果穩定可調
- 自動檢核:報告中出現、但原始統計找不到的 IP / 介面會標註警告;漏分析的設備自動補送
- PDF 日報表:中文字型、P0/P1/P2 色塊表格、頁碼
Graylog REST API(逐小時分頁)
│
▼
前處理(preprocessor.py)
├─ 去除 syslog 標頭、PID 等變動欄位 → 相同訊息歸併計數
├─ Flapping 偵測(DOWN/UP 配對)、過濾正常開關機的零星 link 事件
└─ 依規則分級 P0 / P1 / P2
│
▼
(選用)去識別化 mask.py ── 送出前換成代號
│
▼
LLM(llm.py)
├─ 設備分批、並行分析 → 每台的問題 / 可能原因 / 建議處置
└─ 優先處理表 + 全設備摘要 → 異常總覽
│
▼
(選用)還原代號 → 標題統一改寫 → 自動檢核
│
▼
輸出(output.py / pdf_report.py):terminal / file / pdf / email / line
| 項目 | 說明 |
|---|---|
| Python | 3.10 以上 |
| Graylog | 7.x,需有 API Token |
| LLM | 任一 OpenAI 相容 API(vLLM、LiteLLM…)或 Ollama |
| 字型 | PDF 輸出使用 Windows 內建微軟正黑體(msjh.ttc),找不到時改用細明體 |
pip install -r requirements.txt
truststore讓 Python 使用 Windows 憑證存放區驗證 HTTPS。若內網有 SSL 檢查設備,沒有它會出現CERTIFICATE_VERIFY_FAILED。
copy config.example.yaml config.yamlconfig.yaml 已列入 .gitignore,實際位址不會上傳。主要區塊:
graylog:
url: http://graylog.example.local:9000
llm: # 預設 LLM(內部服務)
provider: openai # openai(vLLM / LiteLLM)或 ollama
url: http://vllm.example.local:8000/v1
model: qwen3.8-flash-next
api_key_env: LLM_API_KEY # 從 .env 讀取的變數名稱
concurrency: 2 # 同時送出的請求數
batch_size: 30 # 每批幾台設備
batch_thinking: false # 設備分析關閉思考(較快、不易耗盡 token)
final_thinking: true # 總覽保留思考
llm_profiles: # 執行時用 --llm-profile <名稱> 覆蓋 llm:
external:
url: https://llm.example.com/v1
model: deepseek-v4.1-flash
api_key_env: EXTERNAL_API_KEY
mask: true # ⚠ 外部服務務必開啟去識別化
thinking_control: false # 非 vLLM/Qwen 不送 enable_thinking 參數完整欄位與說明見 config.example.yaml。
copy .env.example .envGRAYLOG_TOKEN=your_graylog_api_token
LLM_API_KEY=your_internal_llm_key
EXTERNAL_API_KEY=your_external_llm_keyEmail(SMTP_*)與 LINE(LINE_NOTIFY_TOKEN)設定也放在 .env,見 .env.example。
| 情境 | 指令 |
|---|---|
| 分析最近 24 小時並輸出文字 + PDF | python graylog_analyzer.py --hours 24 -o file -o pdf |
| 分析指定日期(台灣時間 00:00~24:00) | python graylog_analyzer.py --date 2026-08-26 -o pdf |
| 改用外部 LLM(自動去識別化) | python graylog_analyzer.py --date 2026-08-26 --llm-profile external -o pdf |
| 預覽實際要送出的內容(不呼叫 LLM) | 任一指令加 --dry-run → reports/send_preview_*.txt |
| 只撈資料存快取、不呼叫 LLM | python graylog_analyzer.py --date 2026-08-26 --fetch-only |
| 用快取重新產生報告 | python graylog_analyzer.py --batch-only -o pdf |
| 每小時撈取 / 從每小時快取彙整 | --fetch-hour / --from-cache --hours 24 |
| 快速模式(單次查詢過去 1 小時) | python graylog_analyzer.py --range 3600 |
輸出方式(-o 可重複指定):terminal、file、pdf、email、line。
使用外部 LLM 服務時,log 中的內網資訊(設備 IP、主機名稱、內部系統名稱、帳號)不應離開內網。本工具在送出前把這些識別資訊換成代號,報告回來後在本機換回真實值;對照表只存在本機記憶體,不會送出。
LLM 判斷故障原因靠的是錯誤訊息本身(例如 Fan 1 is NOT spinning correctly、SNMP_TRAP_LINK_DOWN),不需要知道設備的真實 IP 或名稱;而 IP、筆數等精確數字本來就由 Python 統計,再由程式還原。
| 類別 | 代號 | 偵測方式 | 範例 |
|---|---|---|---|
| 設備(log 來源 IP) | DEV-001 |
統計資料的設備 key,依異常筆數排序編號 | 10.0.56.16 → DEV-001 |
| 主機名稱 | HOST-001 |
syslog 標頭解析出的 hostname;送給模型時直接留空 | SW02-EX46-56.16 → (不送) |
| log 內的其他 IPv4 | IP-001 |
IPv4 正規表示式 | (10.1.2.3) → (IP-003) |
| MAC 位址 | MAC-001 |
aa:bb:cc:dd:ee:ff、aabb.ccdd.eeff 等格式 |
|
| 系統 / 服務名稱 | NAME-001 |
server / group / service-group / virtual-server / pool / role / realm 等關鍵字後的名稱 |
group URL_AppWeb_443 → group NAME-032 |
| 帳號 | USER-001 |
user xxx、User=xxx、for invalid user xxx、for xxx from <IP> |
error for alice from 10.1.2.3 → error for USER-001 from IP-001 |
刻意保留(分析需要且不具識別性):錯誤訊息內容、介面名稱(ge-0/0/25)、ifIndex、通用帳號 root、Realm=LOCAL 等通用值。
原始統計 ──mask_stats()──▶ 代號化統計 ──▶ 外部 LLM
│
報告 ◀── 自動檢核 ◀── 標題改寫 ◀── unmask() ◀┘
- 建立對照表:
Masker.mask_stats()先登記所有設備與主機名稱,再逐一處理訊息樣式;同一個原始值永遠對應同一個代號 - 替換順序:主機名稱(較長,先換以免其中的 IP 片段被拆開)→ IPv4 → MAC → 系統名稱 → 帳號
- 避免誤遮:關鍵字比對以完整字詞為準,並排除常見字(
shutdown、port、local…);例如failed for port 12不會被當成帳號 - 還原:
unmask()把代號換回原值;模型若把代號縮寫成DEV-003/006,會先展開成DEV-003、DEV-006再還原 - 事後檢核:
- 對照表中不存在的代號(模型自行捏造)列在報告末尾「⚠ 無法還原的代號」
- 報告中出現、但原始統計找不到的 IP / 介面列在「⚠ 自動檢核」
- 設備標題由程式統一改寫為「【IP】主機名稱」,不依賴模型輸出格式
python graylog_analyzer.py --batch-only --llm-profile external --dry-run--dry-run 不會送出任何請求,而是把完整的請求內容(system / user prompt)寫入 reports/send_preview_*.txt,可以人工檢查,或用程式比對對照表中的原始值是否出現在檔案中。建議每遇到新的 log 來源(新設備類型、新服務)時先預覽一次,確認沒有新型態的識別資訊。
⚠ 去識別化是降低風險的措施,不是保證。正規表示式只能涵蓋已知的格式;送出前請以
--dry-run確認,並遵守所屬單位的資安規範。
分級由 Python 依 config.yaml 的 priority: 判定(不交給 LLM),任一條件成立即歸入該級:
| 等級 | 條件(預設值) |
|---|---|
| P0 | 單一介面 Flapping ≥ 100 次;單一介面事件 ≥ 300 筆;嚴重硬體關鍵字(風扇不轉、PSU、WRITE_DMA、過熱、storm…)≥ 10 筆 |
| P1 | 單一介面 Flapping ≥ 20 次;單一介面事件 ≥ 50 筆;硬體 / 服務警告(溫度偏高、SFP 光衰、SLB down…)≥ 10 筆;總筆數 ≥ 100;最高等級 ERROR 以上且總筆數 ≥ 50 |
| P2 | 其餘 |
關鍵字以完整字詞比對(避免 PSU 誤中 URL_PSUWeb 之類名稱)。
- 針對
SNMP_TRAP_LINK_DOWN/SNMP_TRAP_LINK_UP事件,DOWN + UP 配對才算一次 up/down - 同一介面 5 分鐘內配對數 ≥ 10 才視為 Flapping
- 非 Flapping 介面的零星 link 事件(正常開關機、拔線)直接過濾,不計入統計
- Graylog 查詢條件包含
SNMP_TRAP_LINK_UP:UP 訊息本身不含異常關鍵字,若不一併撈取就無法配對
PowerShell 排程腳本:
.\run_fetch_hour.ps1 # 每小時:撈前一小時存入 hourly_cache
.\run_daily_report.ps1 # 每天:從快取彙整 24 小時並輸出文字 + PDF建議使用 Windows 工作排程器,排程建立後不需要其他程式開著也能自動執行:
| 任務名稱 | 排程 | 腳本 |
|---|---|---|
\Graylog\Graylog-FetchHour |
每小時整點 | run_fetch_hour.ps1 |
\Graylog\Graylog-DailyReport |
每天早上 08:00 | run_daily_report.ps1 |
graylog-analyzer/
├── graylog_analyzer.py # 主程式(流程、分批並行、覆蓋率檢查、檢核)
├── graylog.py # Graylog REST API 查詢(逐小時分頁)
├── preprocessor.py # 前處理:樣式歸併、Flapping 偵測、分級、檢核
├── mask.py # 去識別化:代號替換與還原
├── llm.py # LLM 呼叫(OpenAI 相容 / Ollama)、重試、dry-run
├── pdf_report.py # Markdown 報告 → PDF(表格、色塊、頁碼)
├── output.py # 輸出模組(terminal / file / pdf / email / line)
├── generate_pdf.py # 將舊版 .txt 報告轉換為 PDF(獨立工具)
├── claude_client.py # Claude API 客戶端(備用)
├── config.example.yaml # 設定檔範例(複製為 config.yaml)
├── .env.example # 機密值範例(複製為 .env)
├── requirements.txt
├── run_daily_report.ps1 # 每日排程腳本
├── run_fetch_hour.ps1 # 每小時排程腳本
├── reports/ # 報告與快取(.gitignore 排除)
└── logs/ # 執行紀錄(.gitignore 排除)
- graylog-mcp:透過 Claude Desktop 以自然語言即時查詢 Graylog 的 MCP Server