Python 实现日志监控

日志监控,是一种外挂式的采集。通过读取进程打印的日志,来进行监控数据的采集与汇聚计算。汇聚成标准的时间序列数据之后,推送给统一的后端存储。日志监控是一种典型的应用、业务监控的手段,如果我们没法在应用程序里内嵌SDK埋点,使用日志监控不失为一种折中方案。

这么说好像还不太明白日志监控到底能够做什么,简单点就是说就是对程序的日志内容进行过滤,如果出现了我们设定的关键字,对其进行计数当达到一定数量时可以触发报警。


下面来看一下需要哪些步骤:

1、不断的监听日志文件,获取最新的日志内容

2、正则功能对日志内容进行过滤

3、计数功能,触发报警


第一步的实现其实很简单,我参考了 python实现tail -f 功能 这篇文章有兴趣的话可以看一下。

思路是什么呢

  • 打开一个文件,把指针移到最后。

  • 每隔1秒钟获取一下日志内容

看一下代码吧

import time
import sys 

file = '/Users/cyt/work/my_python/script/logMonitoring/test.txt'

with open(file,'r') as f:
    f.seek(0,2)
    while True:
        line = f.readline()
        if line and line!= '\n':    #空行不打印
            sys.stdout.write(line)
        time.sleep(1)

这样就可以实现一个不断监听日志文件并打印最新内容的功能了

代码中用到了seek , stdout.write。stdout.writeprint 的区别就是 print 会打印换行符,stdout.write 打印换行符。

在上面的代码中

sys.stdout.write(line) 

#等同

line.strip()
print(line)

这里有个问题,那就是 sleep(1) 当我们同时写入多行内容到一个文件时,会一行一行很慢的打印出来。你可以把时间调小一点,但下面会说一个更好的方法。(你会说直接不用sleep不就行了,你可以尝试一下,然后观察该进程在系统中的cpu使用率)

Python 实现日志监控_第1张图片

你哪怕设置为0.1 都要比不设置sleep好很多,下图是设置为0.1的效果

Python 实现日志监控_第2张图片

使用类实现上面的逻辑,并改进打印速度的问题。

class LogMonitoring():

    def __init__(self,file_name):
        self.__file_name = file_name

    def source(self):
      
        with open(self.__file_name) as f:
            f.seek(0,2)
            
            while True:
                data = f.readlines()
                # 可以添加日志量的监控,如果line列表越长说明单位时间产生的日志越多
                # print(len(line))   

                for line in data: 
                    if line != '\n':    #空行不处理
                        sys.stdout.write(line)
                time.sleep(1)

file = '/Users/cyt/work/my_python/script/logMonitoring/test.txt'
test  = LogMonitoring(file)
test.source()

如何解决刚刚说过的那个问题呢,很简单把f.readline() 替换为 f.readlines()区别想必很多人都知道,这里再说明一下。前者每次获取一行内容,后者是获取到当前指针到末尾所有的内容,返回一个列表每一个列表元素就是一行内容。然后我们遍历这个列表,输出列表元素就可以了。

这个速度是非常快的,相当于不加sleep的速度,其实就算你设置sleep为0.1 如果一下写入上千行内容,那么要近两分钟的时间才能打印完(0.1 * 1000 =100s)。

然后你还可以使用len(line) 获取列表长度,以此来获取单位时间内产生日志的数量,这个单位时间就是sleep的时间。这个单位时间内产生的日志数量,这个指标在某些需求中很有用。

你不用担心列表会占用大量内存的问题,即使是一个长度为1000的列表也不会占用大量的内存(几MB吧),况且应该很难遇到1秒中写入这么多日志的情况吧,即使有也很好解决,调短sleep的时间。或者你可以测试一下一个长度1万的列表会占用多大的内存。使用sys.getsizeof 可以自己玩一下。

Python 实现日志监控_第3张图片

到这里第一个问题应该算是搞定了

剩下的的两个我先把完整代码贴出来再一起说吧

class LogMonitoring():

    def __init__(self,file_name,pattern,threshold):
        self.__file_name = file_name
        self.__pattern = pattern
        self.__threshold = threshold
        self.__count = 0

    def source(self):
        with open(self.__file_name) as f:
            f.seek(0,2)

            while True:
                data = f.readlines()
                # 可以添加日志量的监控,如果line列表越长说明单位时间产生的日志越多
                # print(len(line))   

                for line in data: 
                    if line != '\n':    #空行不处理
                        self.check(line)
                time.sleep(1)

    #匹配规则
    def check(self,data):
        if re.search(f'{self.__pattern}.*?', data):
            self.call()
            return 0
        else:
            return 1

    #报警规则
    def call(self,):
        self.__count += 1
        if self.__count > self.__threshold:
            print('***********')
            

调用逻辑就是,source获取日志内容,把内容给到check方法去过滤如果匹配设置的规则,那么调用call方法进行计数并判断计算器是否超过阀值。

这两个功能我没有测试(因为我突然发现我要做的不是这个东西),整个逻辑就是这样了。

正则的使用可以看这两篇文章: Python 中的正则表达式 和 正则表达式


最后说一些可以优化的点吧。

1、call 报警规则哪里可以完善一下,添加自己想要的告警规则,还可以在写一个告警途径,发挥自己的想象。或者你这里只需要有一个计数的功能,返回计数器的值,结合现成的监控系统去做监控(zabbix就OK)

2、可以建一个单独的配置文件 conf.py ,文件的路径、正则表达式的规则,匹配规则、报警规则等等都可以在这里配置

3、还有一个问题就是程序运行的过程中,日志被备份清空或者切割了,怎么处理。其实很简单,你维护一个指针位置,如果下次循环发现文件指针位置变了(tell()方法返回文件的当前位置,即文件指针当前位置。),从最新的指针位置开始读就行

4、可以考虑使用异步(协程)、线程来提高速度。

这是滴滴开源监控系统夜莺的日志监控,可以参考一下它的功能,使用go语言实现的。

你可能感兴趣的:(运维,Python,python,开发语言)