1. 为什么需要日志监控与警报系统
运维工程师最怕半夜被电话吵醒,而90%的深夜告警都源于未能及时发现系统异常。我曾经历过一次线上事故——磁盘空间在3小时内从30%爆满到100%,导致核心服务崩溃。查看日志才发现,早在空间达到70%时就有大量错误日志输出,但无人关注。这就是典型的"有日志无监控"场景。
日志监控系统的核心价值在于:
- 实时性:传统日志分析是事后行为,而监控是预防性措施
- 自动化:7x24小时不间断扫描,解放人力
- 可追溯:完整记录系统状态变化过程
- 智能预警:在问题恶化前提前干预
Python作为日志监控工具的优势非常明显:
- 丰富的日志处理库(logging、loguru等)
- 强大的文本处理能力(正则表达式、字符串操作)
- 便捷的告警集成(邮件、短信、Webhook)
- 跨平台兼容性(Windows/Linux/macOS通吃)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 监控系统设计要点
2.1 日志源选择策略
不同日志类型需要不同的监控策略:
| 日志类型 | 监控频率 | 典型问题特征 | 处理方式 |
|---|---|---|---|
| 系统日志 | 实时 | 硬件错误、权限异常 | 正则匹配+计数器 |
| 应用日志 | 准实时 | 异常堆栈、业务错误码 | 关键词过滤 |
| 访问日志 | 定时分析 | 流量突增、异常请求模式 | 统计分析 |
| 数据库日志 | 低频率 | 慢查询、锁等待超时 | 阈值告警 |
2.2 核心组件拆解
一个健壮的日志监控系统应包含:
-
日志采集层
- 文件监听:watchdog库监控文件变化
- 日志轮转处理:处理logrotate等工具产生的日志切割
- 多源支持:同时监控多个日志文件
-
处理引擎
- 正则匹配:re库实现复杂模式识别
- 上下文关联:将多行日志关联为单个事件
- 状态跟踪:如连续出现5次错误才触发告警
-
告警模块
- 分级告警:根据严重程度采用不同通知方式
- 告警收敛:相同错误10分钟内不重复告警
- 恢复通知:问题解决后发送恢复确认
3. 实战代码实现
3.1 基础监控框架
python复制import re
from pathlib import Path
import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class LogMonitor(FileSystemEventHandler):
def __init__(self, log_path):
self.log_file = Path(log_path)
self.patterns = {
'ERROR': re.compile(r'ERROR|FAILED|CRITICAL', re.I),
'WARNING': re.compile(r'WARN|TIMEOUT', re.I),
'DISK': re.compile(r'No space left|disk full', re.I)
}
self.alert_counts = {k: 0 for k in self.patterns}
def on_modified(self, event):
if event.src_path == str(self.log_file):
self.analyze_new_lines()
def analyze_new_lines(self):
with open(self.log_file, 'r') as f:
# 只读取新增内容
f.seek(0, 2)
while True:
line = f.readline()
if not line:
break
self.process_line(line)
def process_line(self, line):
for level, pattern in self.patterns.items():
if pattern.search(line):
self.alert_counts[level] += 1
if self.alert_counts[level] % 5 == 0: # 每5次同类错误发一次告警
self.send_alert(level, line)
def send_alert(self, level, context):
# 实际项目中替换为真实告警逻辑
print(f"[{level} ALERT] {context.strip()}")
if __name__ == "__main__":
monitor = LogMonitor('/var/log/syslog')
observer = Observer()
observer.schedule(monitor, path='/var/log')
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
3.2 高级功能增强
日志上下文捕获:
python复制def process_line(self, line):
error_ctx = []
if self.patterns['ERROR'].search(line):
error_ctx.append(line)
# 捕获后续5行作为上下文
with open(self.log_file, 'r') as f:
for _ in range(5):
error_ctx.append(f.readline())
self.send_alert('ERROR', '\n'.join(error_ctx))
频率限制装饰器:
python复制from functools import wraps
import time
def rate_limit(seconds=300):
def decorator(f):
last_run = 0
@wraps(f)
def wrapped(*args, **kwargs):
nonlocal last_run
now = time.time()
if now - last_run >= seconds:
last_run = now
return f(*args, **kwargs)
return wrapped
return decorator
@rate_limit(600) # 每10分钟最多发一次同类告警
def send_alert(self, level, context):
# 告警发送逻辑
4. 生产环境优化建议
4.1 性能调优技巧
-
增量读取优化:
python复制def get_new_lines(self): current_size = self.log_file.stat().st_size if current_size > self.last_size: with open(self.log_file, 'rb') as f: f.seek(self.last_size) content = f.read().decode('utf-8', errors='ignore') self.last_size = current_size return content.splitlines() return [] -
多日志文件处理:
python复制class MultiLogMonitor: def __init__(self, log_files): self.monitors = { path: LogMonitor(path) for path in log_files }
4.2 异常处理要点
必须处理的边界情况:
- 日志文件被rotate时路径不变但inode变化
- 日志文件被删除后重新创建
- 磁盘IO阻塞导致读取超时
- 日志编码格式异常(特别是混合编码时)
推荐的重试机制:
python复制def safe_read(self, path, retries=3):
for i in range(retries):
try:
with open(path, 'r') as f:
return f.read()
except (IOError, PermissionError) as e:
if i == retries - 1:
raise
time.sleep(2 ** i) # 指数退避
5. 告警通道集成方案
5.1 邮件告警示例
python复制import smtplib
from email.mime.text import MIMEText
class EmailAlerter:
def __init__(self, smtp_server, from_addr, to_addrs):
self.smtp = smtp_server
self.from_addr = from_addr
self.to_addrs = to_addrs if isinstance(to_addrs, list) else [to_addrs]
def send(self, subject, content):
msg = MIMEText(content)
msg['Subject'] = subject
msg['From'] = self.from_addr
msg['To'] = ', '.join(self.to_addrs)
with smtplib.SMTP(self.smtp) as server:
server.send_message(msg)
5.2 企业微信机器人集成
python复制import requests
import json
class WeChatWorkAlerter:
def __init__(self, webhook_url):
self.webhook = webhook_url
def send(self, content):
payload = {
"msgtype": "text",
"text": {
"content": content,
"mentioned_mobile_list": ["13800001111"] # 需要@的人
}
}
requests.post(self.webhook,
data=json.dumps(payload),
headers={'Content-Type': 'application/json'})
5.3 告警升级策略
建议的分级处理方案:
- Level1(普通告警):邮件通知
- Level2(重要告警):邮件+即时消息
- Level3(紧急告警):电话呼叫+所有渠道广播
实现示例:
python复制def dispatch_alert(self, level, content):
if level == 'WARNING':
self.email_alerter.send(f"[WARN] {content}", content)
elif level == 'ERROR':
self.im_alerter.send(content)
self.email_alerter.send(f"[URGENT] {content}", content)
elif level == 'CRITICAL':
self.call_alerter.trigger()
self.im_alerter.send("@all "+content)
6. 实战中的经验教训
-
时间格式统一:所有日志时间建议转换为UTC存储,显示时再按需转换时区
-
告警风暴防护:
- 设置合理的静默期(如相同错误15分钟内不重复告警)
- 实现告警聚合(将相似告警合并发送)
-
测试策略:
python复制def test_alert_flow(self): # 注入测试日志 with open(self.log_file, 'a') as f: f.write("ERROR: Test alert trigger\n") # 验证告警是否触发 assert self.alerter.last_alert == "ERROR" -
日志采样技巧:对于高频日志,可采用随机采样方式减轻负载
python复制if random.random() < 0.1: # 10%采样率 self.process_line(line) -
配置化实践:将监控规则移出代码
yaml复制# rules.yaml rules: - name: disk_full pattern: "No space left|disk full" level: CRITICAL threshold: 1
