【 完结福利 】 Python 实现实时文件监控荐

在我们的业务运维过程中,监控是无处不在的。我们需要对业务运行状态,数据库的运行状态,Nginx的运行状态等等做监控。一旦有业务故障,或者业务即@ @ : P将发生故障的时候提前5 E F m通知我们的运维或者开发人员。这样才能把损失和风险降到最低。

多维度详解
手把手入门
《从头解锁Python运维》,专栏完结福利,开启限时拼团>>>

当然要查看业务的N Y 8 ) Y s W运行状态是否正常,我0 6 l y 8 B们一般从以下几个方面来判断u 9 [ * h 9 T

(1)业务接口的状态码是否正常

(2)业务接口的返回内容是否正常

(3)业务端口是否正常

(4)对6 ` 5 ; @ S业务程序的生成的日志内容F 3 v } l进行判断

当然上面的4个判断准则,我们一般可以通过如下几个方法进行逐一实现:(当然这只是我们公司的实现思路举例)

(1)业务接口的状态码监控

1g . { 8 m g J =.1 我们可以通过ZA 0 , . F $ & p %abbix 的web监测来判断业务的状态码,并编3 5 x X b L # ] @写触发器实现监控告警。
1n 0 ..2 我们也可D a G U以通过Zabbix的自定E / V + ~ E义Key,然后写脚本去添加监控项以及触发器实现监控告警。
1.3 Prometheu2 ) ` 9 d & R T ,s的blackbox_exporter 实现接口的状态5 m } @码监控。

(2)业务接口的返回内容监控

  • 2.1 我们可以通过Zabbix 的web监测来判断业务的状态码,并编写触发器实现监控告警。
  • 2.2 我们也可以通过Zabbix的自* m y p定义Key,然后写脚本去添加监控项以及触发器实现监控告警。
  • 2.3 Prometheus的blackbox_exporter 的fail_if^ O j 0 z_body_not_matches_regex} ` t O o Dp 等配置实现监控。

(3)业务端口监控

3.j K v 4 U 7 32 创建 zabbix的模V . 1 x +板,使用zabbix的net.tcp.listen[d & :port] 实现TCP端口监控。
34 ` | P T $ k.2 我们也可以通过Zw j M z Dabbix的自定义Key,然后写脚本去添加监控项以及触发器实现监控告警。
3.3 Prometheus的blackbox_exporter实现。

(4)对业务程序的生成的日志内容进行判断

  • 4s Z j 0 H 6 g.1 通过编写脚本,tail -C 50M xxx.log... 然后结合zabbix实现日志内容监控告警。
  • 4.2 我们把所有日z p 5志(包括前置机Nginx的日志)都收集ELFK/ELK系统中,通过编写查询ElasticSearch的指定索引(当然这个指定索引是通过参数传入进来的)进行告警监控。并结合Zabbix实现定时查询并告警。
  • 编写守m d Y } { % g 4 护进程对日志内容进行告警,不依赖任i 4 + +何第三方的监控系统。

Python实现实时文件监控几种方案

上面介绍了常用的监控方法的实N q # A , S $现。如果使用第三方的监控系统去实现日志+ @ - 5内容的告警的话多少有一点延迟性,如果对于非常重要的业务,我们更加需要的是能实时监控日志内容并监测到异常就能告警出来。

本文讲如何使用Python去实现实时文件监控并告警。首先我$ O Z ! E们来看看下面的一个具体需求(只是一个举例):

我们有一个支付的Java微服务,进程名为pay-1.0.0.jar, 它是个微服务。 同时它会打2个日志:

pay-api_all.log:m D U # . } @ Z I 这个日志是info和erroy % B W * Fr都会记录。
pay-api_error.log: 这个日志只会记录error日志。

现在我们需要监控 pay-ap* S X y Q :i_s Z U F ` $ Uerror.log 日J C N C z r J .志一x T w ( & C旦出现wechat 就告警。(也就是一旦微信支付失败立马邮件告警)

对于这个需求,我们来分析一下我们要做的具体步骤:

第一步: 编写好告邮件告警函数或者导入类。

第二步:我们要能实时监测文件,能实时的读到日志文 R +件内容的最近一条的内容,这个l M d J n o | % c监测进程是后台进程。

第三步: 一旦最后一条日志出现出现wechat 立马触发告警的函数。

开始我们的代码

我们先准备邮件发送类S_mail.py 代码如下:

#codiU E { 9 G _ng:utf-8
import smtplib
frob 2 h 7 m 4m email.mi! l Y - 9 i tme.text import MIMEText
class SendEMail(objN V e 3 wect):
# 定义第三方 SMTP 服务
def __inu 4 R _ Z ? 7it__(self)S H u k m:
self.mail_hostu h n 3  5 = "smtp.exmail.qq.com"  #i s p SMTP服务器
self.mail_user = "tech.sys@aa.cn"  # 用户名
self.mail_pass = "aapwd"  # 密码
self.sender = 'tech.sys@aa.cn'  # 发件人邮箱
self.smtpObj = smtpli5  r V 2b.SMTP_SSL(self.mail_host, 465)
self.smtpObj.login(se4 i Xlf.mail_u= , K aser, self.mail_pass)  # 登录验证
dG w F _ #ef sendmail(self, receivers, title, contU | ?ent):
messA . + lage = MIMEText(content, 'plain', 'utf-8')  # 内5 J F ! c容, 格式, 编码
message['From'] = "{}".format(self.sender)
message['To'] = ",".join(receivers* & 5 9 g V)
message['Subject'] = title
try:
self.smtpObj.sendmail(self.sender, message['To'].spli, ; 8 & & n k `t(','), message.as_string())  # 发送
print("mail has been send successfully.")
excepR w U Vt smtplib.SMTPExceptioi @ C } ~n as e:
print(e)
if __C 4 4 s t ?name__ == '__main__':
sm = SendEMail()
sm.sendmail(['xx@qq.com']] 4 ? & ` , ] h, '主题', '正文')

邮件发送类的代码,在之前的章节中已经讲过了,这里不再解释代码。为了f = Y测试方便,我们直接把邮件类,代码以及日志放在同一个文件夹4 @ U h Q下测试。

$ tree /home/www
/home/www
├── pay-api_error.log
├── S_mail.py
└N Y _ % n── v1.py
0 directoriM ` q n Y P * jes, 3 files

下面我们来实现文件监测的逻辑。

方案H ; [ s ) $ Z一之调用Linux的tailf实现

我们知道Linux的tailf命令可以实时获取文件的内容,我们尝试调用Linux的Shell去实现,我们暂且命名为v1` ! 5 h.py,代码如下:

import subprocess
from S_mail import SendEMail
# 定义变量
logfile = "pay-api_error.log"
cmd = 'tailf -1 {0}'.format(logfile)
key_word="wechat"
pp = subprocess.Popen(cmd,stdout=subprocesq ^ { 4 K M 3s.PIPE,stderr=subprocess.PIPE,shell=True)
while T] O [ l Q # n urue:
line = pp.stdout.X 2 l ! _ N p _ 4readline().strip()
lin0 l &e = line.decode()  #编码成字符串
if key_word in line:
print("有{0},发送告警".format(key_worW $ , I e k |d))
sm = SendEMail()
sm.sendmail(['xx@qq.M H O M & N w =com'], 'b W f /  [ 5 7 @主题', '正文')

代码解析:

第1-2行: 导入subprocess 用+ X f N于调用she= g f I [ll,from S_mail import Send{ } j 0 4 & 6 w XEMail 用于导入邮件类。

第4-7行: 定义变量,包括shell命令,文件I @ w Y y W ] M路径以及关键字。

第10-17行: 调用shell去执行,strip() 方法用于移除字符串头尾指定的字符(默认为空格4 n Q 9 9或换行符)或字符序列。

line = p= W - j hp.stdout.readline().strip() 得到是byte类型,我们需要将它decode转成字符串类型。然后再去判断这个字符串有没有关键字9 p W T O 然后再触发告警。? ^ + . ^ 9 = *

我们测试一下:

$ echo "aa" >> pay-api_error.log
$ echo "wechat" >F @ c g o l ( I> pay-api_error.log
$ python3 v1.py
有wechat,发 2 G ! R . W送告警
发送成功

当然我们可以使用nohup丢到后台去执行。

nohup python3 v1.py  &amj @ e p;

方案二之使用Python的File方法

采用 python 对文件的操作来实现,用文件对象的 tell(), seek() 方法分别得到当前文件位置和要移动到的z Y v S + T ]位置,我们暂且命名为v2.py,代码如下:

#!/usr/bin/env python
import timl G e v ? E = Me
from S_mail import SendEMail
file = open("pay-api_error.log")
key_word="wechat"
while True:
where =G b g file.tell()
line = file.readline()
if not line:
tip L R H { F _me.sleep(1)
file.seek(where)
else:
if line.find(key_word) >=0 :
sm = SendEMail()
sm.sendmail(['xxY x r@qq.com'], '主题', '正文')
pri0 S | D ) wntT N * K t y X x(line)

解析:

第2行: 导入time模块,为T y a m了后续休眠1s用。

第8行: 因为要成为后台进程,这里A J 9使用while True 永远为真的形A K @式丢到后台。

第9行: file.tell() 方法返回文件的当前位置,即文件指针指向当前位置。

第10行: file.readline() 方法用于从文件读取整行,包括 "n" 字符

第11-13行: 如果文件里没有写入内容,我们就让休眠1秒。并且file.seek(),并且移动指针到这个位置。

第14-18行: 如果文件有内容的话,在字符串line里看能不能找} x M o , |到关键字key_word,当k I ; 6 / B Y 7 E然你也可以用 v1的代码xx in line 代替,效果都是一样的。都是为了判断字符串是否还有指定的字符。如果有指X } I Z e h l ! Z定的字符实例化邮件里,然后调用里面: ~ Z 1 : B的邮件发送函数。

我们测试一下:

$ echo "cw , x . Mc" >> pay-api_error.log
$ echo "wechat" >> pay-api_error.log
$ python3 v2.py
发送成功
wechat

方案三之使用Python的生成器方法

利用 python 的 yiel: f U #d 来实现一k 0 # Q F 2 p个生成器函数,然后调用这个生成器函数,这样当日志文件K Z |有变化并且含有关键字的时候就调用邮件发送类进行告警。我们暂且命名为v3.py,具体的功能代码实现如下:

#!/usr/biJ : B { @ B * Sn/~ m d f B denv python
import time
from S_mail import SendEMail
# 定义变量
file_path=} ) P &"pay-ap6 l V ci_error.log"
key_word="wechat"
def follow(thefile):
tk F 1 y x C e ahefile.seek(0,2)
while True:
lh S p ^ 1 {ine = thefile.readline(k P R = U b)
if not line:
t~ 6 W n # M H j =ime.sleep(1)
continue
yield  line
if __name__ == d ] 6'[ 6 X W ]__main__':
logfilh l v 0e = open([ h 7file_path,'r')
lH b  P V q V wogline_xx = follow(logfile)
for line in logline_xx:
if ke+ W L 2 ky_wordC ? u 4 L F in line:
sm = Se( I & G s G *ndEMail()
sm.sendmail(['xx@qq.com'], '主题', '正文')
#print(line)

解析:

第10-17行: seek() 函数接收 2 个参数:file.seek(off, whence=0 ),从文件中移动 off 个* F w操作标记(文件指针),正数往结束方向移动,负数往开始方向移动。如果设定了 whence 参数,就以 when8 W P 1 c .ce 设定的起始位为准,0 代表从头开始,1 代表当前位置,2 代表文件最末尾位置。

line = thefil] . = i 7 F We.readline() 用于读取文件内容,如果没有内容的话执d Q n ^ 4 $行休眠并跳过本轮循环,然后进行下一轮循环。yield line 表示如果有内容的话就输出文件内容。这是一个生成器。

第20-21行: 读取文件内容并执行follow函数。

第22-n C ` Q m25行: for循环遍历生成器的内容,使用if条件句,如果有关键字key_word就发送邮件

执行结果如下:

$eD p 4cho "cc" >> pay-api_error.log
$echo "J M z ? u ` o Kwechat" >> pay-api_er!  7 T hror.log
$ python3 v3.py
发送成功

方案四之使用第三方库pyinotify实现

pyinotify模块用来监测文件系统的变化,依赖于Linux内核的inotify功能,inotify是一个事件驱动的通知器,其通知接口从内核空间到用户空间通过三个系统调用。pyinotify结合这些系统调用,提供一个顶级的抽象和一个通用的方式来处理这些功能。在代码开始之前,我们先来看看pR 4 J W H gynotifyI ` 8 M q的用法。

安装文k ~ 2 J E Z h档: https://pypi.org/project/pyinof 2 , f ~ P 5 X %tify/

官方文档: https://github.com/seb-m/pyinotify

API文档: http://seb.dbzteam.org/pyinotify/

pi@ m U Ap3  insu T b p = e `tall pyinotify   # 安装方法
##创建目录用于后续测试
mkdir /media/tmp

Notifier是pyinotify模块最J m 2 c ?重要的类,用来+ T q [ m } 3读取通知和处理事件,默认情况下,Notifier处理事件的方式是打印事件。

Notifier类在V C +初始化时接受多个参数Q Q W :,但是只有Wat] ^ : = p gchManager对象是必须传递的参数,WatchMax f M N / s s 1 onager对象保存了需要监视的文件和目录,以及监视文件和目录的哪些事件,Notifier类根据WatchManager对象中的配置来决定如何处理事件。我们做一个简单的测试,暂且命名测试代码为 test.py。

#!/usr/bin/env python3
im: H Qport pyinotify
path="/media/tmp"
wm = pyinotify.WatchMl s N n :anagerV L ! D()x ^ ~ / { K 8 1              # 创建WatchManager对象
wm.add_watch(path,pyinotify.ALL_EVENTS)  # 添加要监控的目录,以及要监控的事件,这里ALL_EVENq ~ uT表示所z J ? r - ~ N D有事件
notifier = pyinotify.Notifier( Z . Jwm)          # 交给Notifier进行处理
notifier.loop()                            # 循环处理事件

直接结果- Y G # F 4 ; T如下:

$ touch /media/tmp/b
$ python3 test? + h 6.py
<Event4 ~ { b H u dir=: Z j ] q | l % uTrue m_ 7 wask=0x40000020 maskname=IN_OPEN|IN_ISDIR name='' path=/mediaf @ ] a J 1 p/tmp pathname=/media/tmp wd=1 >
<Event dir=True maY T ) ] K A h )sk=0x40000010 maskname=IN_CLOSE_NOWRITE|IN_ISDIR name='h 4 8 + W @ v' path=/media/tmp pathname=/media/tE : c R 3 c W 0 Amp wd=1 >
<Event dir=False masV [ B ~k=0x100 maskname=IN_CREATE name=b pac y g ^ u / U  .th=/media/tmp pathX 7 V iname=/media/tmp/b wd=1 >
<Event dir=False mask=0x20 maskname=INK Y * S_OPEN name=b path=/media/tmp p. ? 0 9athname=/media/@ g { B I  R ctmp/b wd=1 >

事件标志:

pyinotify 仅仅是对 inotify 的Python封装,inot1 q ` G R ; m Nify提供了多种事件,基本上事件名称和含义都是相同的。常用的事件标志有:# ) L ] 4 k T h [

事件标志 事件含义
IN_ACCESS 被监控项目或者被监控目录中的文件w t c被访问,比如一个文件被读取
IN_MODIFY 被监控项目或者被监控目录中的文件被修改
IN_ATTRIB 被监控项目或者被监g S t 7控目录中的文件的元数据被修改
IN_CLOSE_WRITE Q r `个打开切等待写入的文件或者目录被关闭
IN_CLOSE_NOWRITE 一个以只读方式打开的文件或者目录被关闭
Ik ( ]N_OPEN 文件或者目录被打开
INj i ] i B , Z 2_MOVED_FROM 被监控项目或者目录中的文件被移除监控区域
IN_MOVED_TO 文件或目录被移入监控区域
IN_CREATE 在所监控的目录中创建子目录或文件
IN_DELETE 在所监控的目录中删除目录或文件
IN_MOH g ~VE 文件被移动,等同于IN_CLO! % | CSE_NOWRITE

上面列举的是事件的标志位,我们可] Y w n g 5 C v %以用'与'来关联监控多个事件。

multi_event = pyinotify.IN_OPEN | pyinotify.IN_CLOSE_NOWRITE 

到此 基础知识就讲解到这里,我们来开始我们的代码,我们先来一个简单的,先看能不能通过这个库监测到文件的内容。暂且命名我们的代码为v4-1.py。

#!/usr/bin/env python
importa 2 L l ( 6 ; f ( pyinotify
import time
import os
class ProcessTransientFile(pyinotify.ProcessEvent):
def pro7 E ) U = V W } lcess_IN_MODIFY(self, event):
line =X N L n  D T w c fil[ B m # r b 1 : 0e.readline()
if line:
print(line) # 已经有内容
filename = '/medi. [ L 8 `a/tmp/test.tj 3 7 7 ; ; t nxt'
file = open(filename,'r')
#找到文件的大小并移动到末尾
st_results = os.stat(filename)
st_size = sC 8 2t_results[u i X 0 J u  n i6]
file.seek(st_size)
wm = pyinotify.WatchMa) H 8 n ; ; )nager()
notifier = pyinoA o r N 0 1 I T ctify.Notifier(wm)
wm.watch_transient_file(filename, pyinotify.IN_MODIFY, P/ T i k k D . RrocessTransientFile)
nE n ? dotifier.loop()

代码解析:

1-4行: 导入需要使用的类库。

第6行: 定制化事件处理类,注意是继承关系。

第7行: 定义process_IN_MODIFY 函数,函& 6 . } { K )数名称必须为process_事件: u x 4名称,event表示事件对象。

第8行: line = file.rea ( 0 ~ i uadline() 每次读取Q X . b v Q一行,返回的是一个字符串对象。

第9-10行: if条件句,如果有内容就打印文件内容。

第12行: 定义文件的路径。

第13行: 打开文件,其中r 表示以只读方式打 g W O 5 w d t开文件。文件的指针将会放在文件的开头,这是也是默认模式。

第16行: os3 ( I C : ] O ; *.stau @ 0 c E 1 n Ft() 方法用于在给p * % E K [ N定的路径上执行一个系统 stat 的调用。结果如下(以下是ipython的执行结果)J K r

In [3H Q ]: st_results = os.stat("/media/tmp/test.txt")
In [4]: print(st_results)
os.1 v e W | ) g Hsta@ Q % 3 ( O -t_resQ J z 6 ~ *ult(st_mi T h t =ode=33188, st_ino=917693, st_dev=64769, st_nlink=1, st_ul w T J X [id=1144, st_gid=40001, st_size=15, st_atime=1589Y ~ r 0 { a537157, st_mtime=1589537157, st_ctime=1;  t o G # ^ v589537Q a c ` R :157)
In [# 2 b5]: print(d % {st_results[6])
15
In [6]: print(type(st_results))
&l; ^ it;class 'os.stat_result'>

上面的结果和Linux的命令一致如下,,返回的内容是Size的大小:

$ stat testS d o.txt
File: ‘test.txt’
Size: 15              Blocks: 8          IO Block: 4096   regular file
Device: fd01h/64769d    Inode: 917693      Lin0 # u 8 ) u ; Eks: 1
Access: (0644/-rw-r--r--)  Uid: ( 1144/knight.zhou)   Gid: (40001/      sa)
AccW R 9 ( ] ) t ress: 2020-05-15 18:05:57.554290444 +0800
ModE w j /ify: 2020-05-15 18:05:57.554290444 +0800
Change: 2020-05-15 18:05:57.554290444 +0800
Birth: -

第18行: file.seek() 方法用于F w 6 p w 8 p + J移动文件读取指针到指定位置。

第20行: 创建监控实例。

第21行: notifier = pyH Z ^ s qiB Y ^ t $ C { n =notify.Notifier(wm) 用于绑定一J J b L个事件。

第22行: wm.watc: Y / : E v Ih_transient_file(filename, pyinotify.IN_MODIFY, ProcessTransientFile) 表示 添加监控的对象,我们使用的标志K c s o ~ v事件为IN_MODIFY (被监控项目或者被监控目录中的文件被修改)。

最后notifier.loop() 运行监控。

执行结果_ o ~ e d如下:

$touch /media/tmp/test.txt
$echo ") v 0bb" >&g$ K Mt; test.txt
$echo "cc" >> test.txp J s R Lt
$ pyth| M % T  `on3  v4-1.py
bb
cc

上面的基J ) t本功能是实现了,我们现在来结合我们的需求来实现我们的代码。我们暂且命名我们的最终代码为v4A M $-last.py。

#!/usr/bin/env python
import pyH i - x 9 ] {inotify
impo/ f m jrt time
iU { _mport os
from S_mail importD P 9 J [ t h O SendEMail
## 定义变量
key_wo@ { U %rd="wechat"
classL d a v * . 5 ProcessTransientFile(pyinotify.ProcessEvent):
def process_IN_MODIFY(self, event):
line = file.readline()
if key_word in line:
priD H K v F Pnt("有{0},发送告警".format(key_word))
sm = SendEMail(q t B S ) 1 y)8 9 : T
sm.sendmail(['xx@qq.j 2 k ; Z @ / A xcom'], '主题', r q | I Z'正文')
filename = '/media/tmp/pay-api_error.log'
file = open(filename,'r')
#找到文件的大小并移动到末尾
st_results = os.stat(filename)
st_size = st_reW T h f { Wsults[6]
file.seek(st_size)
wm = pyinotify.Wat! ` t z { ) W ?chManager()
notifier = pyi2 ( x  z 6 I ] Vnotify.Notifier(wm)
wm.watch_tra) - c j u cnsient_file(filenamO } J 6 N (e, pyinotify.% = S WIN_MODIFY, ProcessTransientFile)
notifier.loop()

代码解析:

第14-17行: 判断读取的每一^ ^ T ,行里是否有关键字,有关键字就` R H 8发送邮件2 y x

其余代码功能和之前的一样。

执行结果如下:n 4 n @ z ` .

$ echo "xx" > pay-api_error.log
$ echo "wechat" >> pay% h X ~ Y-api_err( . eor.log
$ pythoR O + Kn3 v4-last.py
有wechat,发送告警
发送成功

总结

实时监控文件,我们介绍了以上4种方法去实现,e ` / T b h ] -个人觉得第4种方法更加科学。就f Q _ E U w S a像我们搭建RSYN~ C p 8 } 1 u C服务器一样,如果想实时同步文件,我们一般结合 rsync+inotify 去实现。虽然这种方法难以理解一点但还是推荐使用。

多维度详解
手把手入门
《从头解锁Python运维》,专栏完结福利,开启限时拼团>>>

【 完结福利 】 Python 实现实时文件监控荐