在我们的业务运维过程中,监控是无处不在的。我们需要对业务的运行状态,数据库的运行状态,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 去实现。虽然这种方法难以理解一点但还是推荐使用。
发表评论