Python3获取mysql慢sql日志内容并飞书预警

实现思路:全文件扫描,匹配对应时间开始的日志,获取该匹配行之后的内容 。

# -*- coding: utf-8 -*- 
import os  
import re  
import requests
import subprocess  
from datetime import datetime
  
def send_zabbix_alert(zabbix_key, value, host):  
    """  
    使用 zabbix_sender 向 Zabbix Server 发送告警信息。  
  
    参数:  
    - zabbix_server: Zabbix Server 的地址。  
    - zabbix_port: Zabbix Server 的端口号。  
    - zabbix_key: Zabbix 中的 Item Key。  
    - value: 要发送的值,通常为 1 表示告警,0 表示正常。  
    - host: Zabbix 中定义的主机名。  
  
    返回:  
    - zabbix_sender 的执行结果。  
    """  
    # 构建 zabbix_sender 命令  
    # 注意:这里的 zabbix_sender 路径需要根据你的系统环境进行替换  
    zabbix_sender_path = '/usr/bin/zabbix_sender'  
    # 使用 -z 指定 Zabbix Server,-p 指定端口,-s 指定主机名,-k 指定 Item Key,-o 指定值  
    command = [  
        zabbix_sender_path,  
        '-z', "zabbix.com",  
        '-p', str(10051),  
        '-s', host,  
        '-k', zabbix_key,  
        '-o', str(value)  
    ]  
  
    # 执行命令并获取输出  
    try:  
        #result = subprocess.run(command, capture_output=True, text=True, check=True)  
        result = subprocess.check_output(command, stderr=subprocess.STDOUT)

        print("告警发送成功:", result.stdout)  
        return result.stdout  
    except subprocess.CalledProcessError as e:  
        print("告警发送失败:", e.stderr)  
        return e.stderr  
  
def push_report(web_hook,content):
    # 使用正则表达式去除所有只包含空白字符(包括换行符)的行
    cleaned_content = re.sub(r'^\s*$\n?', '', content, flags=re.MULTILINE)
    header = {
        "Content-Type": "application/json;charset=UTF-8"
    }
    message_body = {
        "msg_type": "text",
        "content": {
            "text": cleaned_content
        }
 
    }
 
    ChatRob = requests.post(url=web_hook, json=message_body, headers=header)
    opener = ChatRob.json()
 
    if opener["StatusMessage"] == "success":
        print(u"%s 通知消息发送成功!" % opener)
    else:
        print(u"通知消息发送失败,原因:{}".format(opener))
 
 

def load_last_scan_timestamp():  
    try:  
        with open(last_scan_timestamp_file, 'r') as f:  
            return datetime.strptime(f.read().strip(), '%Y-%m-%dT%H:%M:%S')  
    except FileNotFoundError:  
        return None  
  
def save_last_scan_timestamp(timestamp):  
    with open(last_scan_timestamp_file, 'w') as f:  
        f.write(timestamp.strftime('%Y-%m-%dT%H:%M:%S'))  

def read_file_line_by_line(path):
    with open(path, 'r') as file:
        for line in file:
            yield line.strip()

def parse_slow_query_log(path, start_timestamp):
    new_queries = []
    current_query = []
    in_query = False
    for line in read_file_line_by_line(path):
        # 查找时间戳行,这标志着新查询的开始
        if line.startswith('# Time:'):
            timestamp_str = line.split()[2].replace('T', ' ').split('.')[0]
            timestamp = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S')

            # 如果当前不在查询中,并且时间戳大于开始时间戳,则开始新查询
            # 注意:这里不再跳过当前行,而是将其添加到current_query中
            if not in_query and (start_timestamp is None or timestamp > start_timestamp):
                in_query = True
                current_query = ["P5数据库"]  # 将# Time:行作为查询的第一行
                last_timestamp = timestamp  # 记录时间戳
            else:
                # 如果已经在查询中,但遇到了新的# Time:行,可能是日志格式问题或连续查询
                # 这里可以记录一个错误或警告,但在这个示例中,我们只处理第一个查询
                # 你可以根据需要调整这个行为
                if in_query:
                    # 结束前一个查询并可能开始新的(这里我们不开始新的,只是记录并继续当前查询)
                    full_query = '\n'.join(current_query)
                    new_queries.append((last_timestamp, full_query))
                    in_query = False  # 理论上这里不应该设置为False,除非你确定要结束当前查询
                    # 但由于我们假设每个# Time:都对应一个新的查询,这里不执行结束逻辑

            # 更新上一个时间戳(对于处理日志突然结束的情况,但在这个逻辑中可能不是必需的)
            # 因为我们已经将时间戳与查询内容一起记录了
            # last_timestamp = timestamp  # 注释掉,因为不再需要单独记录last_timestamp

        # 如果当前在处理查询,则继续收集所有行
        if in_query:
            current_query.append(line.strip())

    # 处理最后一个查询(如果有的话)
    # 由于我们不再跳过# Time:行,并且每次遇到# Time:都可能开始新查询,
    # 因此最后一个查询(如果有)应该已经在上面的循环中被处理了

    # 但是,为了安全起见,我们可以再次检查current_query(尽管在正常情况下它应该是空的)
    if in_query and current_query:
        # 这通常不应该发生,除非日志以查询开始但缺少结束的时间戳
        # 我们可以选择记录一个错误,或者像之前一样使用datetime.now()作为时间戳
        full_query = '\n'.join(current_query)
        # 假设我们使用当前时间作为时间戳(尽管这通常不是最佳实践)
        new_queries.append((datetime.now(), full_query))
        # 或者,更安全的做法是抛出一个异常,指出日志格式可能有问题
        # raise ValueError("Unexpected end of log file while parsing a query.")

    return new_queries
  
# 主程序  
if __name__ == '__main__':  
    last_scan_timestamp_file = '/hskj/script/last_scan_timestamp.txt'
    slow_query_log_path = '/hadata/mysql/slow_query.log'
    last_scan_timestamp = load_last_scan_timestamp()  
    new_queries = parse_slow_query_log(slow_query_log_path, last_scan_timestamp)  
    # webhook 来自于 获取机器人webhook:复制webhook 中的那个值
    webhook = "https://"
    
    if not new_queries:
        print("No new slow queries detected.")

    # 更新上次扫描的时间戳为最后一个查询的时间戳(或更精确的处理方式)  
    if new_queries:  
        save_last_scan_timestamp(max(timestamp for timestamp, _ in new_queries))
    # 处理新查询(例如,打印)  
    for timestamp, query in new_queries:
        #print(f"New Slow Query Detected at {timestamp}:")  
        push_report(webhook,query)
        query = re.sub(r"^\s*$\n?","",query, flags=re.MULTILINE)
        #send_zabbix_alert("slowsqlstr",query,"mjq-")
        print(query)  
        print("="*80)

告警内容样例:

 如果对你有帮助,一块也是爱

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

容器云服务

如果对你有用,一块钱也是爱。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值