菜鸟记录自己的python成长历程
使用Python监控Syslog Trap!监控OSPF邻居状态!再对Syslog进行基本分词(与上课相同)写入数据库并分析
最终结果如下图, Python对OSPF邻居状态改变的Syslog Trap进行分析,并打印!
读取数据库!产生SYSLOG严重级别分布图
读取数据库!产生SYSLOG日志源分布图
这实验为上次实验的升级版:https://blog.csdn.net/u012092175/article/details/114275206
直接进入正题
一:思路
1:对于监控syslog trap,首先得确认在监控设备端需要发起syslog信息到服务器。
2:不过由于送snmp变成了syslog,也就是说,处理数据更加的“人性化”了,不再是人类难以理解的代码了。
syslog举例:<189>188: *Mar 3 13:14:33: %OSPF-5-ADJCHG: Process 1, Nbr 2.2.2.2 on GigabitEthernet4 from FULL to DOWN, Neighbor Down: Interface down or detached
我们需要做的只需要监控syslog的端口,收到数据后对数据进行分词。
3:对于需求:由于需要输出状态,所以我们需要判断ospf的log信息
4:将获取的syslog进行分词,批量的写入数据库中
5:读取数据库,再使用matplotlib模块创造饼状图
二:实现
1:对于监控syslog trap,首先得确认在监控设备端需要发起syslog信息到服务器。
参考:python基础之socket与socketserver - 招财大龙猫 - 博客园 https://www.cnblogs.com/hls91/p/11714827.html
1.1需要使用到的模块如下:
import socketserver # 做服务器
import re # 做分词匹配
from dateutil import parser # 日期
import os # 系统内独写文件等操作
import sqlite3 # 数据库
1.2:定义服务器监听的处理方法:
class SyslogUDPHandler(socketserver.BaseRequestHandler):
def handle(self):
data = bytes.decode(self.request[0].strip()) # 读取数据
print(data)
1.3:启动服务器
def syslog_server(HOST = '0.0.0.0',PORT = int(514)):
try:
server = socketserver.UDPServer((HOST, PORT), SyslogUDPHandler) # 绑定本地地址,端口和syslog处理方法
print("Syslog 服务已启用, 写入日志到数据库!!!")
server.serve_forever(poll_interval=0.5) # 运行服务器,和轮询间隔
except (IOError, SystemExit): # 奔溃则重新启动
raise
1.4:网络环境与之前相同,CSR配置了syslog
CSR syslog 设定:
logging source-interface GigabitEthernet1
logging host 192.168.6.222
logging trap 6 # 默认为6
1.5:触发syslog,查看一下效果
2:我们需要做的只需要监控syslog的端口,收到数据后对数据进行分词。
通过假设syslog服务器,已经能够正常的收到syslog日志信息,次是我们观察一下syslog的格式,将他进行格式化处理
以关键词:%OSPF-5-ADJCHG 作为触发点,使用re模块通过正则表达式来匹配需要的内容。
为方便理解,可以查看以下对比
<189>188: *Mar 3 13:14:33: %OSPF-5-ADJCHG: Process 1, Nbr 2.2.2.2 on GigabitEthernet4 from FULL to DOWN, Neighbor Down: Interface down or detached
.* %OSPF-5-ADJCHG: Process (\d+), Nbr (\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}) on (\w+\d+) from (\w+) to (\w+), .*
一共5个元素,既0~4;
3:输出状态,所以我们需要判断ospf的log信息:
3.1 代码实现
# <189>188: *Mar 3 13:14:33: %OSPF-5-ADJCHG: Process 1, Nbr 2.2.2.2 on GigabitEthernet4 from FULL to DOWN, Neighbor Down: Interface down or detached
if 'OSPF-5-ADJCHG' in str(data):
ospf_info = re.match(r'.*%OSPF-5-ADJCHG: Process (\d+), Nbr (\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}) on (\w+\d+) from (\w+) to (\w+),.*',str(data)).groups()
print(f'OSPF process {ospf_info[0]} Neighbor {ospf_info[1]} status {ospf_info[4]}')
4:将获取的syslog进行分词,批量的写入数据库中
4.1 写入数据库之前,我们需要设计数据库的表项(自己做分词挺麻烦的,后期可以用ELK来做,更加的简单方便),
syslog的facility对应关系,可以参考:https://www.geek-share.com/detail/2659540658.html
cisco-syslog-severity-levels:0 – Emergency 1 – Alert 2 – Critical 3 – Error 4 – Warning 5 – Notice 6 – Informational 7 – Debug
在本次模板的syslog中,我们以以下日志信息为例
<187>83: *Apr 4 00:03:12.969: %LINK-3-UPDOWN: Interface GigabitEthernet2, changed state to up
以下为备注说明:
['facility'] = 187 >> 3 = 23 # 前5位为facility,所以向右移位操作>>3获取前5位
['facility_name'] = 23 对应 local7
['logid'] = 83
['time'] = 00:03:12.969
['log_source'] = LINK
['severity_level'] = 3
['severity_level_name'] = error
['description'] = UPDOWN
['text'] = Interface GigabitEthernet2, changed state to up
补充说明:
<187>83: *Apr 4 00:03:12.969: %LINK-3-UPDOWN: Interface GigabitEthernet2, changed state to up
187之所以做位移是因为:187 转 二进制 = 1011 1011
二进制其中前5位代表的是facility既1011 1,后三位代表的是优先级011
将1011 1填充至8位后 = 0001 0111 = 23
将011填充至8位后 = 0000 0011 = 3
既对应facility = 23 = Local7 ; severity_level 对应 %LINK-3-UPDOWN = 3
定义facility,severity_level
# facility与ID的对应关系的字典(手动定义)
facility_dict = {0: 'KERN',
1: 'USER',
2: 'MAIL',
3: 'DAEMON',
4: 'AUTH',
5: 'SYSLOG',
6: 'LPR',
7: 'NEWS',
8: 'UUCP',
9: 'CRON',
10: 'AUTHPRIV',
11: 'FTP',
16: 'LOCAL0',
17: 'LOCAL1',
18: 'LOCAL2',
19: 'LOCAL3',
20: 'LOCAL4',
21: 'LOCAL5',
22: 'LOCAL6',
23: 'LOCAL7'}
# severity_level与ID的对应关系的字典
severity_level_dict = {0: 'EMERG',
1: 'ALERT',
2: 'CRIT',
3: 'ERR',
4: 'WARNING',
5: 'NOTICE',
6: 'INFO',
7: 'DEBUG'}
分词代码如下:
syslog_info_dict = {'device_ip': self.client_address[0]} # 记录client的IP
try:
syslog_info = re.match(r'^<(\d*)>(\d*): \*(.*): %(\w+)-(\d)-(\w+): (.*)', str(data)).groups() # 通过正则匹配需要的内容
# 后3位为severity_level & 0b111 获取后3位
syslog_info_dict['facility'] = (int(syslog_info[0]) >> 3)
syslog_info_dict['facility_name'] = facility_dict[int(syslog_info[0]) >> 3]
syslog_info_dict['logid'] = int(syslog_info[1])
syslog_info_dict['time'] = parser.parse(syslog_info[2])
syslog_info_dict['log_source'] = syslog_info[3]
syslog_info_dict['severity_level'] = int(syslog_info[4])
syslog_info_dict['severity_level_name'] = severity_level_dict[int(syslog_info[4])]
syslog_info_dict['description'] = syslog_info[5]
syslog_info_dict['text'] = syslog_info[6]
except AttributeError:
# 有些日志会缺失%SYS-5-CONFIG_I, 造成第一个正则表达式无法匹配 , 也无法提取severity_level
# 下面的icmp的debug就是示例
# <191>91: *Apr 4 00:12:29.616: ICMP: echo reply rcvd, src 10.1.1.80, dst 10.1.1.253, topology BASE, dscp 0 topoid 0
syslog_info = re.match(r'^<(\d*)>(\d*): \*(.*): (\w+): (.*)', str(data)).groups()
print(syslog_info[0])
syslog_info_dict['facility'] = (int(syslog_info[0]) >> 3)
syslog_info_dict['facility_name'] = facility_dict[int(syslog_info[0]) >> 3]
syslog_info_dict['logid'] = int(syslog_info[1])
syslog_info_dict['time'] = parser.parse(syslog_info[2])
syslog_info_dict['log_source'] = syslog_info[3]
# 如果在文本部分解析不了severity_level, 切换到syslog_info[0]去获取
# 185 二进制为 1011 1001
# 前5位为facility >> 3 获取前5位
# 后3位为severity_level & 0b111 获取后3位
syslog_info_dict['severity_level'] = (int(syslog_info[0]) & 0b111)
syslog_info_dict['severity_level_name'] = severity_level_dict[(int(syslog_info[0]) & 0b111)]
syslog_info_dict['description'] = 'N/A'
syslog_info_dict['text'] = syslog_info[4]
链接并将syslog分词后写入数据库代码如下:
conn = sqlite3.connect(gl_dbname)
cursor = conn.cursor()
cursor.execute("insert into syslogdb (time, \
device_ip, \
facility, \
facility_name, \
severity_level, \
severity_level_name, \
logid, \
log_source, \
description, \
text) values ('%s', '%s', %d, '%s', %d, '%s', %d, '%s', '%s', '%s')" % (syslog_info_dict['time'],
syslog_info_dict['device_ip'],
syslog_info_dict['facility'],
syslog_info_dict['facility_name'],
syslog_info_dict['severity_level'],
syslog_info_dict['severity_level_name'],
syslog_info_dict['logid'],
syslog_info_dict['log_source'],
syslog_info_dict['description'],
syslog_info_dict['text'],
))
conn.commit()
实验环境下创建数据库代码:
gl_dbname = 'syslog.sqlite'
# 实验环境,检测是否存在数据库,如果存在,则删除数据库,重新再创建一个空的数据库
if os.path.exists(gl_dbname):
os.remove(gl_dbname)
# 连接数据库
conn = sqlite3.connect(gl_dbname)
cursor = conn.cursor()
# 创建数据库
cursor.execute("create table syslogdb(id INTEGER PRIMARY KEY AUTOINCREMENT,\
time timestamp, \
device_ip varchar(32),\
facility int,\
facility_name varchar(32),\
severity_level int,\
severity_level_name varchar(32),\
logid int,\
log_source varchar(32), \
description varchar(128), \
text varchar(1024)\
)")
conn.commit()
5:读取数据库,再使用matplotlib模块创造饼状图
5.1 对于用matplotlib做图表,实际上我们可以通过JavaScript来做,但是其实还可以用更加方便快捷的Kibana来实现,具体也是后续ELK的内容,暂时不管
创建饼状图就按照以下固定参数进行吧,感觉没什么特别需要记录的
以下为plt的处理方法:
import sqlite3
from dateutil import parser
from matplotlib import pyplot as plt
from practice_syslog_server_to_db import severity_level_dict
def syslog_show2(name_list, count_list, show_name):
plt.rcParams['font.sans-serif'] = ['Simhei'] # 设置中文
plt.figure(figsize=(6, 6))# 调节图形大小,宽,高
# 使用count_list的比例来绘制饼图
# 使用level_list作为注释
patches, l_text, p_text = plt.pie(count_list,
labels=name_list,
labeldistance=1.1,
autopct='%3.1f%%',
shadow=False,
startangle=90,
pctdistance=0.6)
'''
# labeldistance,文本的位置离远点有多远,1.1指1.1倍半径的位置
# autopct,圆里面的文本格式,%3.1f%%表示小数有三位,整数有一位的浮点数
# shadow,饼是否有阴影
# startangle,起始角度,0,表示从0开始逆时针转,为第一块。一般选择从90度开始比较好看
# pctdistance,百分比的text离圆心的距离
# patches, l_texts, p_texts,为了得到饼图的返回值,p_texts饼图内部文本的,l_texts饼图外label的文本
'''
# 改变文本的大小
# 方法是把每一个text遍历。调用set_size方法设置它的属性
for t in l_text:
t.set_size = 30
for t in p_text:
t.set_size = 20
# 设置x,y轴刻度一致,这样饼图才能是圆的
plt.axis('equal')
plt.title(show_name) # 设定主题,show_name是输入的元素
plt.legend()
plt.show()
5.2 接下来就是连接数据库,读取需要的信息,统计数量并且生成表格
# 连接数据库
conn = sqlite3.connect('syslog.sqlite')
cursor = conn.cursor()
5.3 获取严重级别分布图
重点是需要知道数据库的语法,让它输出名称,以及统计名称对应的数量。
# 获取严重级别
cursor.execute("select severity_level_name,COUNT(*) as count from syslogdb group by severity_level_name")
yourresults = cursor.fetchall()
severity_level_name_list = []
severity_level_count_list = []
for severity_level_name_count in yourresults:
severity_level_name_list.append(severity_level_name_count[0])
severity_level_count_list.append(severity_level_name_count[1])
syslog_show2(severity_level_name_list, severity_level_count_list, 'SYSLOG严重级别分布图')
5.4 日志源分布图
cursor.execute("select log_source,COUNT(*) as count from syslogdb group by log_source")
yourresults = cursor.fetchall()
facility_name_name_list = []
facility_name_count_list = []
for facility_name_name_count in yourresults:
facility_name_name_list.append(facility_name_name_count[0])
facility_name_count_list.append(facility_name_name_count[1])
syslog_show2(facility_name_name_list, facility_name_count_list, '日志源分布图')
6:效果展示
6.1 获取严重级别分布图
6.2 日志源分布图