日志分析与阈值告警

实时 tail 日志、正则提取指标、超阈值触发告警与摘要报表。

场景:监控告警 技术栈:Python 来源:GitHub 参考

在线演示

0有效日志行
0触发告警窗口
0ERROR / 5xx 条数
0%错误占比

等待操作:粘贴日志或点击“载入示例日志”,再点“分析日志”。

项目简介

服务日志里藏着最真实的运行状态。本页实现一个日志监控告警器:持续 tail 日志文件,用正则提取错误码与耗时指标,窗口内错误数或平均耗时超过阈值时输出告警并生成摘要,可对接企业微信 / 钉钉 webhook。

代码示例:tail 与指标提取

py
import re
import time
from collections import deque
from pathlib import Path

ERROR_RE = re.compile(r"(ERROR|Exception|Traceback)")
LATENCY_RE = re.compile(r"latency=([\d.]+)ms")
WINDOW_SEC = 60
ERROR_THRESHOLD = 5
LATENCY_THRESHOLD = 800.0

def follow(file: Path):
    with file.open(encoding="utf-8", errors="ignore") as f:
        f.seek(0, 2)  # 从文件末尾开始
        while True:
            line = f.readline()
            if line:
                yield line
            else:
                time.sleep(0.2)

def run(log_path: Path):
    errors: deque[float] = deque()
    latencies: deque[float] = deque()
    for line in follow(log_path):
        t = time.time()
        if ERROR_RE.search(line):
            errors.append(t)
        m = LATENCY_RE.search(line)
        if m:
            latencies.append((t, float(m.group(1))))
        # 清理窗口外数据
        while errors and errors[0] < t - WINDOW_SEC: errors.popleft()
        while latencies and latencies[0][0] < t - WINDOW_SEC: latencies.popleft()
        avg = sum(v for _, v in latencies) / len(latencies) if latencies else 0
        if len(errors) > ERROR_THRESHOLD:
            alert(f"{len(errors)} errors in {WINDOW_SEC}s")
            errors.clear()
        if latencies and avg > LATENCY_THRESHOLD:
            alert(f"avg latency {avg:.0f}ms > {LATENCY_THRESHOLD}ms")
            latencies.clear()

def alert(msg: str) -> None:
    print(f"[ALERT] {time.strftime('%H:%M:%S')} {msg}")  # 可替换为 webhook 推送

操作步骤

  1. 按你的日志格式调整 ERROR_RE 与 LATENCY_RE 正则,阈值常量按需修改。
  2. 运行 python log_alert.py /var/log/app/app.log 即可实时监控。
  3. 把 alert() 替换为 requests 调用企业微信 / 钉钉机器人 webhook,实现移动端告警。

完整可运行工程

可直接运行零第三方依赖Python 3.8+

完整工程(非片段):解析、聚合、告警三段逻辑集中在 log_alert.py,页面上的在线演示与它使用同一套判定规则(级别词优先、再退回状态码),因此演示里看到的结论就是命令行会得到的结论。

log-alert/
  ├── log_alert.py     # 解析 / 聚合 / 告警 + CLI
  ├── test_log_alert.py # 最小用例回归
  ├── requirements.txt
  └── README.md

log_alert.py

核心实现:parse / level_of / bucketize / run_report,含 --selftest。

python
#!/usr/bin/env python3
"""日志指标提取与阈值告警(零第三方依赖,仅标准库)

用法:
    python log_alert.py access.log --threshold 3 --window 60
    python log_alert.py app.log --json
    tail -n 500 access.log | python log_alert.py -
    python log_alert.py --selftest
"""
import argparse
import json
import re
import sys
from datetime import datetime
from pathlib import Path

ACCESS = re.compile(r'^(\S+) \S+ \S+ \[([^\]]+)\] "(\w+) ([^"]*?) [^"]*" (\d{3}) (\d+|-)')
APP_TIME = re.compile(r"^(\d{2}/\w{3}/\d{4})\s+([\d:]+)")
ERR_PAT = re.compile(r"\bERROR\b|\bFATAL\b|Exception|Traceback")
WARN_PAT = re.compile(r"\bWARN(?:ING)?\b")


def level_of(status, line):
    """级别判定顺序:显式级别词 > HTTP 状态码。避免把 "GET /api/errors 200" 误判为错误。"""
    if ERR_PAT.search(line):
        return "ERROR"
    if WARN_PAT.search(line):
        return "WARN"
    if status is not None:
        return "ERROR" if status >= 500 else ("WARN" if status >= 400 else "INFO")
    return "INFO"


TIME_RE = re.compile(r"(?

test_log_alert.py

unittest 回归:级别判定、误判防护、时间槽聚合、阈值敏感度。

python
"""log_alert.py 最小用例回归(标准库 unittest)"""
import unittest

from log_alert import bucketize, fmt_slot, level_of, parse, run_report, SAMPLE


class TestLogAlert(unittest.TestCase):
    def test_level_from_status(self):
        self.assertEqual(level_of(500, 'GET /x HTTP/1.1" 500'), "ERROR")
        self.assertEqual(level_of(404, 'GET /x HTTP/1.1" 404'), "WARN")
        self.assertEqual(level_of(200, 'GET /x HTTP/1.1" 200'), "INFO")

    def test_path_keyword_not_misjudged(self):
        self.assertEqual(level_of(200, 'GET /api/errors HTTP/1.1" 200'), "INFO")

    def test_explicit_level_word(self):
        self.assertEqual(level_of(None, "18/Sep/2026 13:57:31 [ERROR] db pool exhausted"), "ERROR")
        self.assertEqual(level_of(None, "18/Sep/2026 13:57:33 [WARN] slow query"), "WARN")

    def test_parse_counts(self):
        p = parse(SAMPLE)
        self.assertEqual(len(p["rows"]), 7)
        self.assertEqual(p["skipped"], 0)

    def test_bucket_and_fmt(self):
        b = bucketize(parse(SAMPLE)["rows"])
        self.assertEqual(len(b), 3)
        self.assertEqual(fmt_slot(13 * 3600 + 55 * 60), "13:55")

    def test_alert_threshold_sensitivity(self):
        self.assertEqual(len(run_report(SAMPLE, 99, 60)["alerts"]), 0)
        self.assertGreaterEqual(len(run_report(SAMPLE, 1, 60)["alerts"]), 1)


if __name__ == "__main__":
    unittest.main(verbosity=2)

requirements.txt

依赖清单:仅标准库。

text
# 本工程仅使用 Python 标准库(argparse / re / json / pathlib / datetime)。
# 不需要安装任何第三方包;此文件保留是为了统一 `pip install -r requirements.txt` 流程。

README.md

运行方式、支持的日志格式、退出码约定。

markdown
# 日志指标提取与阈值告警(log-alert)

读日志 → 判级别 → 按时间窗口聚合 → 超阈值告警,全程零第三方依赖。
既适合 `tail -f | python log_alert.py -` 做轻量巡检,也能作为接入 Prometheus 之前的过渡方案。

## 文件说明

| 文件 | 作用 |
|---|---|
| `log_alert.py` | 解析 / 聚合 / 告警核心 + CLI 入口 |
| `test_log_alert.py` | unittest 最小用例回归 |
| `requirements.txt` | 依赖清单(本工程无第三方依赖) |

## 运行

```bash
python -m pip install -r requirements.txt        # 无第三方依赖,可跳过
python log_alert.py access.log --threshold 3 --window 60
python log_alert.py access.log --json
tail -n 500 /var/log/nginx/access.log | python log_alert.py -
python log_alert.py --selftest
python test_log_alert.py
```

## 支持的日志格式

1. **访问日志**(Nginx / Apache combined):
   `IP - - [18/Sep/2026:13:55:02 +0800] "POST /api/pay HTTP/1.1" 500 218`
2. **应用日志**(行首为时间戳的文本日志):
   `18/Sep/2026 13:57:31 [ERROR] db pool exhausted`

级别判定优先看显式级别词(ERROR / FATAL / Exception / Traceback、WARN),其次看 HTTP 状态码
(500+ → ERROR,400–499 → WARN,其余 INFO)。因此 `/api/errors` 这类**正常返回 200 的路径不会被误判**。

## 退出码约定

| 退出码 | 含义 |
|---|---|
| 0 | 解析成功且未触发告警 |
| 2 | 触发告警(可被 cron / CI 直接捕获) |
| 1 | 参数或输入错误 |

阈值与窗口通过 `--threshold` / `--window` 传入,默认「60 秒内 ERROR ≥ 3 条」。

运行标注

  1. 可直接运行:保存 4 个文件到同一目录,执行 python log_alert.py 你的日志.log --threshold 3。
  2. 需按需修改的配置项:阈值与窗口用 --threshold / --window 传参(默认 3 条 / 60 秒);若日志时间格式与示例不同,改 log_alert.py 顶部的 ACCESS / APP_TIME 两个正则即可。不改也能直接跑。
  3. 配套用法:tail -n 500 access.log | python log_alert.py - 可做滚动巡检;触发告警时退出码为 2。
  4. 自测:python log_alert.py --selftest(或 python test_log_alert.py),6 个用例应全部通过。
复制结果为 # ===== 文件名 ===== 分隔的多文件文本,按文件名分别保存即可;也可逐块用右上角“复制代码”单独取用。

来源参考