服务进程在后台运行时,错误不会主动跳到开发者眼前。常见的做法是登录服务器执行 tail -f 观察输出,可一旦离开终端,异常信息就可能被淹没在滚动日志里。更实际的方式是让脚本替我们盯着文件,发现指定关键字后立刻发邮件。下面介绍的方案不依赖重型监控系统,只需要Python标准库中的 smtplib 和 email 模块,再配合一个持久化偏移量的小文件即可运行。

一、增量读取日志:把文件偏移量存下来
日志文件可能很大,如果每次扫描都从第一行开始读取,不仅浪费CPU和IO,还会重复发送已经处理过的历史错误。解决办法是维护一个偏移量,记录上次读取到文件的哪个字节位置。Python的文件对象提供 seek 和 tell 方法,seek 可以把内部指针移动到指定位置,tell 则返回当前指针位置。第一次运行时偏移量为0,读取结束后把最新的 tell 值写入一个小文件或数据库中,下次启动时从该位置继续读取。
下面的函数实现了最基础的增量读取:传入日志路径和上次偏移量,打开文件后先移动到旧位置,再按行读取新增内容,最后返回新的偏移量。这里特别指定 errors='ignore',是为了防止日志中混入非UTF-8字符导致脚本直接崩溃。
def read_new_lines(log_path, position):
with open(log_path, 'r', encoding='utf-8', errors='ignore') as f:
f.seek(position)
lines = f.readlines()
new_position = f.tell()
return lines, new_position
这个实现有两个容易忽略的细节。第一,如果日志文件被程序重写或清空,文件大小可能小于旧偏移量,此时 seek 到一个超出文件末尾的位置不会报错,但读取结果为空,tell 可能仍然返回旧值;更稳妥的做法是先比较文件大小和偏移量,若文件变小则重置为0。第二,实际项目中的日志经常按天切割,例如 app.log 重命名为 app.log.1,再新建 app.log,这时候如果只盯着固定路径,就会漏掉切割后新文件的内容。对于简单场景可以接受,如果需要更强的可靠性,可以结合文件 inode 变化或使用专门的日志库。
除了手动管理偏移量,也可以直接把监控脚本挂在 tail -f 的输出上,但那种方式难以在异常时插入报警逻辑,而且进程重启后需要重新建立管道。基于偏移量的轮询方案虽然存在秒级延迟,但实现透明、可控性更强,适合自建轻量监控。
二、用smtplib发送报警邮件
Python标准库提供了 smtplib 模块用于连接SMTP服务器,email 模块负责构造邮件内容。最常见的做法是使用 SMTP_SSL 连接465端口,服务器全程走加密通道,适合大多数企业邮箱和第三方邮件服务。如果使用587端口,通常需要先建立普通连接,再调用 starttls 升级为加密连接。这里以465端口为例进行封装。
import smtplib
from email.mime.text import MIMEText
from email.header import Header
def send_mail(subject, content, receivers):
smtp_server = 'smtp.ipipp.com'
smtp_port = 465
sender = 'monitor@ipipp.com'
password = 'your_password'
msg = MIMEText(content, 'plain', 'utf-8')
msg['Subject'] = Header(subject, 'utf-8')
msg['From'] = sender
msg['To'] = ','.join(receivers)
server = smtplib.SMTP_SSL(smtp_server, smtp_port, timeout=10)
try:
server.login(sender, password)
server.sendmail(sender, receivers, msg.as_string())
finally:
server.quit()
代码中 MIMEText 的第一个参数是邮件正文,第二个参数 plain 表示纯文本格式。如果希望报警内容更醒目,可以把 plain 改成 html,并在正文中嵌入简单的表格或颜色标签,但要注意有些邮箱会拦截HTML内容,纯文本反而更稳定。Header 用来处理中文主题,否则直接赋值可能出现乱码。登录密码不要硬编码在脚本里,建议使用环境变量或配置文件读取,并通过文件权限限制访问。
发送动作本身可能因为网络抖动、认证失败或限流而抛异常。监控脚本不能因为一次邮件发送失败就整个退出,否则会漏掉后续更严重的错误。比较合适的做法是捕获 smtplib.SMTPException 以及 OSError,记录失败日志并等待下一轮重试。也可以把待发送的报警内容暂时写入本地队列,由另一个发送线程异步处理,不过对于小规模监控来说,同步发送已经足够。
如果公司内部没有开放SMTP服务,还可以接入第三方邮件API,例如使用 requests 调用HTTP接口发送通知。但标准库方案胜在零额外依赖,部署起来最省事。
三、组合监控规则与防刷机制
有了增量读取和邮件发送两个基础能力,接下来需要把它们串成一个完整的监控循环。监控脚本启动后会先读取上次保存的偏移量,然后每隔几秒检查一次日志文件,把新增行交给正则表达式匹配。匹配规则不要只写一个固定的 ERROR 字符串,最好支持可配置的关键字列表或正则,例如 error、exception、timeout、traceback 等。下面是一个带冷却时间的示例。
import re
import time
def monitor_log(log_path, pattern, receivers, interval=5, cooldown=60):
position = 0
last_sent = 0
while True:
try:
lines, position = read_new_lines(log_path, position)
errors = [line.strip() for line in lines if re.search(pattern, line)]
if errors and time.time() - last_sent > cooldown:
content = '\n'.join(errors[-20:])
send_mail('日志报警', content, receivers)
last_sent = time.time()
except Exception as exc:
print(f'monitor loop error: {exc}')
time.sleep(interval)
冷却时间 cooldown 的作用是防止短时间内刷出成百上千条相同错误时,邮件被连续发送造成告警风暴。示例中只取最近20条错误作为邮件正文,既保留了关键信息,又避免正文过长被邮件服务器拒收。冷却时间过去后,如果又有新的错误出现才会再次发送。这个机制虽然简单,但已经能应对大部分日志突发场景。
更进一步,可以把监控规则配置化,例如用JSON文件描述多个日志路径、匹配模式、接收人列表和不同冷却时间。这样新增一个监控目标只需要修改配置,不用改动代码。日志轮转的问题也可以在这一层解决:定期检查文件大小,如果发现当前文件大小小于上次偏移量,说明日志可能被截断或切割,此时把偏移量重置为0,并记录一条状态日志。
这套方案适合测试环境、内部工具或小规模线上服务的补充告警。如果业务日志量很大、需要跨机器聚合或按错误类型分派通知,还是建议考虑 Graylog、ELK、Loki 这类专业日志平台。但在资源有限或者需要快速响应时,用Python自己写一个几十行的监控脚本,往往比引入整套系统更快见效,也更容易调整规则。
Python日志监控邮件报警SMTP修改时间:2026-09-22 06:38:01