问题描述:

有个业务经常出问题,无奈只好在 源头就开始监控告警,在处理端,源头端,目标端全部做相应措施。我这里只演示源头端监控告警,监控细粒度直接干到日志级别

项目场景

1.我有6台机器:

主机名

ip地址

test1

192.168.5.11

test2

192.168.5.12

test3

192.168.5.13

test4

192.168.5.14

test5

192.168.5.15

test6

192.168.5.16

2.我的程序会被调度组件调度到以上的任意一台机器上跑,且日志会打到这台机器的标准路径/data/test/log下
3.我的日志格式是这样的test_id.log格式,如下:

[root@test1 log] ls
test_12.log test_13.log test_15.log test_212.log test_312.log

4.第3步的id(主键自增1)存在数据库test_id表里,test_id表字段如下:

字段

数据类型

id

int

name

varchar(20)

实际数据如下:

id

name

231

test1流程

232

test2流程

233

test1流程

235

test2流程

236

test1流程

238

test1流程

以test1流程为例,每次重启会生成新的id,比如上一次的id是235,重启后新生成的id就会增1,而且一个流程只能有一个现在时间点的id,大小小的都是历史id
mysql>select id from test where name='test1流程 ';

231

test1流程

233

test1流程

236

test1流程

238

test1流程

像这样,这个时候,test1流程的id就是238,231和233 236是他历史的id

5.调度组件将我的程序调度到哪台机器上去了,并没有存在数据库里(这里我们后面自己写函数去到指定目录查找)

6.现在的需求就是: 对test1流程现在的日志实时监控,并发现关键字error就告警,比如现在日志文件是:test_ 238.log,在哪台机器上不知道


函数分析:

算法思路:
1.要有告警的动作,邮件告警,那这里可以写一个函数
2.通过name找到这个id是多少
3.找到id后,通过ssh到结点指定目录查找,找到了,该结点就是日志文件所在结点
4.找到结点后,通过paramiko模块实现远程控制,打印远程机器的日志,或者监控远程日志
5.拿到日志得做出判断,什么时候开始告警

所有东西都根据算法来,算法不会就细致点拆分步骤,代码不熟练一点都不重要,不用纠结if,for,函数格式是如何的都是可以搬砖,拿来抄,能把东西做出来就ok


解决方案:

开始上才艺,根据算法,一步一步来,在主节点192.168.5.11上操作:
1.邮件告警函数编写:

def send_mail(body):
    # 邮箱登录信息
    sender = 'youruername@163.com'
    password = 'yourpasswd'
    # 收件人邮箱,也可以是前面的youruername@163.com,反正只要能收到就行
    receiver = 'xxxx@qq.com'
    # 邮件内容
    subject = 'Python邮件示例'
    #message = '这是一封通过Python发送的test邮件。'
    # 构造邮件对象
    msg = MIMEText(body, 'plain', 'utf-8')
    msg['From'] = Header(sender)
    msg['To'] = Header(receiver)
    msg['Subject'] = Header(subject)
    try:
      # 实例化smtp对象,连接SMTP服务器
      server = smtplib.SMTP('smtp.163.com', 25)
      server.login(sender, password)
      # 发送邮件,调用sendmail方法
      server.sendmail(sender, receiver, msg.as_string())
      print('日志异常,邮件发送成功')
    except Exception as e:
      print('业务异常,邮件发送失败:', str(e))
    finally:
      # 关闭连接
      server.quit()
#send_mail('这是一个邮件测试')

编写完后将这个测试一下,是否能发送邮件成功

日志监控器ddms_日志监控器ddms


2.钉钉等告警媒介函数编写,我这里这是演示,不过大概的函数都差不多:

def send_message_to_group(url, message):
    header = {
        "Content-Type": "application/json"
    }
    data = {
        "msg_type":"text",
        "content": {
            "text": f"{message}"
            }
    }
    rsp = requests.post(url=url, headers=header, json=data)
    print(rsp.text)

3.数据库里拿id并返回id号,数据库操作:

def get_id_info():
    print("开始数据库操作,获取id")
    # 打开数据库进行链接 host 主机名或ip  user:用户名 password:密码 database:数据库名称
    db = pymysql.connect(host="192.168.5.11", user="test_user", password="test_passwd", port=3306,database="test_db")
    # 使用cursor方法创建一个游标对象
    cursor = db.cursor()
    # 使用 execute 方法执行sql 查询,因为每次只能拿到最大的那个(业务特点),所以倒序排序
    cursor.execute("select  id from test where name='test1流程' order by id  desc limit 1;")
    # 使用 fetchall 方法获取多条数据  单条数据用 fetchone()
    data = cursor.fetchall()
    # 输出数据
    db.close() 
    # 关闭数据库  
    return data

4.找到id对应的日志文件对应机器的ip地址:

def find_id_node(id,hosts,path):
    pkey = paramiko.RSAKey.from_private_key_file('/root/.ssh/id_rsa')
    command = f'ls {path}|grep {id}'
    found_host = None
    for ip in hosts:
        print(f'正在{ip}上找是否存在id号是{id}') 
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(hostname=ip, port=22, username='root', pkey=pkey)
        stdin,stdout,stderr = ssh.exec_command(command)
        # 获取命令执行结果
        output = stdout.read().decode('utf-8')
        error = stderr.read().decode('utf-8')
        # 判断文件是否存在
        #print(output)
        #如果error为空,output为真
        if not error and output:
            found_host = ip
            break
        # 关闭SSH连接
        ssh.close()
    if found_host:
       print(f'id号是{id}的文件在主机 {found_host} 上找到')
       logging.info(f'id号是{id}的文件在主机 {found_host} 上找到')
       output = output.strip("\n")
       return found_host,output
    else:
       print('文件未找到')

5.远程监控日志,使用paramiko模块get_transport方法:

def ssh_monitor_log(host,test_path,log_file):
    print(host)
    path = os.path.join(test_path,log_file)
    pkey = paramiko.RSAKey.from_private_key_file('/root/.ssh/id_rsa')
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(hostname=host, port=22, username='root', pkey=pkey)
    # 创建SSH会话
    transport = ssh.get_transport()
    channel = transport.open_session()
    channel.exec_command(f"tail -f {path}")
    try:
        #print(stdout)
        # 实时获取输出
        while True:
           if channel.recv_ready():
              output = channel.recv(1024).decode('utf-8')
              if output:
                 output = output.lower()
                 if 'error' in output:
                    index = output.index(error)
                    start_index = max(0, index - 10)  # 关键字前10个字符的起始索引
                    end_index = min(len(output), index + len('error') + 50)  # 关键字后50个字符的结束索引
                    err_message = output[start_index:end_index]
                    message = f"test1流程,在{host}机器上,日志文件是{log_file}有报错请检查: \n {err_message}"
                    send_message_to_group(url, message)
                    send_mail(err_message)
    except Exception as e:
        print('监控业务日志异常:', str(e)) 
        logging.info('监控业务日志异常:', str(e)) 
        message = f"监控业务日志异常:, {str(e)}"
        send_message_to_group(url, message)
        send_mail(message)
    finally:
        # 关闭SSH连接
        channel.close()
        transport.close()
        ssh.close()

6.正文:

# 配置日志输出到文件
logging.basicConfig(filename='monitor_test.log', level=logging.INFO)        
id = get_id_info()[0][0]
hostip = ['192.168.5.11','192.168.5.12','192.168.5.13','192.168.5.14','192.168.5.15','192.168.5.16']
test_path = '/data/test/log/'
#取函数返回值,结果是元组
id_host_and_logfile = find_id_node(id,hostip,test_path)
#ip地址赋值给id_host
id_host = id_host_and_logfile[0]
logging.info(f'id_host is {id_host}')
print(f'id_host is {id_host}')
#日志文件赋值给log_file
log_file = id_host_and_logfile[1]
#path = os.path.join(test_path,log_file)
#钉钉或者其他群组的私密信息
url = 'https://yourgroup/your_secret'
ssh_monitor_log(id_host,test_path,log_file)

7.导入模块:

import smtplib
import tailer
from email.mime.text import MIMEText
from email.header import Header
import requests
import pymysql
import paramiko
import os
import socket
import logging

8.其他:
如果想在192.168.5.11,实时打印日志,怎么改程序:
def ssh_monitor_log改一下,这里不演示截图了

transport = ssh.get_transport()
    channel = transport.open_session()
    channel.exec_command(f"tail -f {path}")
    try:
        #print(stdout)
        # 实时获取输出
        while True:
           if channel.recv_ready():
              output = channel.recv(1024).decode('utf-8')
              #改成这样
              if output:
                 print(output)