forked from iti-framework/iTi-Flask
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
6.3 KiB
Python
170 lines
6.3 KiB
Python
from sqlalchemy.orm import noload
|
|
from sqlalchemy.sql._typing import ColumnExpressionArgument
|
|
from typing import List, Dict, Any, Optional, Set
|
|
from iti.applications.extensions import db
|
|
from flask import current_app
|
|
from iti.applications.models import (
|
|
IotAlertLog,
|
|
IotAlertRule,
|
|
IotAlertPush,
|
|
IotNode,
|
|
IotEndpoint,
|
|
)
|
|
from sqlalchemy import select, distinct
|
|
from simpleeval import simple_eval
|
|
|
|
def delete_node_alert_rule(node: IotNode):
|
|
"""
|
|
删除节点告警规则
|
|
Args:
|
|
node: 节点对象
|
|
"""
|
|
db.session.query(IotAlertRule).filter_by(node_id=node.id).delete()
|
|
db.session.commit()
|
|
|
|
def add_endpoint_alert_log(endpoint_id: int):
|
|
"""
|
|
添加采集端告警日志
|
|
Args:
|
|
endpoint_id: 采集端ID
|
|
"""
|
|
endpoint = db.session.scalar(select(IotEndpoint).filter_by(id=endpoint_id))
|
|
if not endpoint:
|
|
return "采集端不存在"
|
|
alert_tag = f"ep{endpoint.id}_nd0"
|
|
alert_log = db.session.scalar(select(IotAlertLog).filter_by(alert_tag=alert_tag))
|
|
if alert_log:
|
|
alert_log.trigger_count += 1
|
|
if alert_log.trigger_count >= 3:
|
|
alert_log.status = 1
|
|
push_alert(alert_log)
|
|
else:
|
|
dict_data = dict(
|
|
alert_tag=alert_tag,
|
|
alert_target_name=f"{endpoint.endpoint_name}({endpoint.endpoint_number})",
|
|
alert_level=1,
|
|
status=0,
|
|
trigger_count=1,
|
|
alert_content=f"采集端 {endpoint.endpoint_name}({endpoint.endpoint_number}) 网络不可达",
|
|
)
|
|
alert_log = IotAlertLog(**dict_data)
|
|
|
|
db.session.add(alert_log)
|
|
db.session.commit()
|
|
return ""
|
|
|
|
def add_node_alert_log(node_id: int, alert_value: str):
|
|
"""
|
|
添加节点值异常告警日志
|
|
Args:
|
|
node_id: 节点ID
|
|
alert_value: 告警值
|
|
"""
|
|
node = db.session.scalar(select(IotNode).filter_by(id=node_id))
|
|
if not node:
|
|
return "节点不存在"
|
|
|
|
alert_rule_list = db.session.scalars(select(IotAlertRule).filter_by(node_id=node_id)).all()
|
|
if len(alert_rule_list) > 0:
|
|
for alert_rule in alert_rule_list:
|
|
if alert_rule.status == 1:
|
|
# 根据data_type转换alert_value
|
|
value_data = get_value(node.data_type, alert_value)
|
|
# 检查告警值是否满足触发规则
|
|
if is_alert_trigger(alert_rule.alert_rule, value_data):
|
|
alert_tag = f"ep{node.endpoint_id}_nd{node_id}"
|
|
alert_log = db.session.scalar(select(IotAlertLog).filter_by(alert_tag=alert_tag))
|
|
if alert_log:
|
|
alert_log.trigger_count += 1
|
|
alert_log.alert_content = complete_alert_text(node, alert_rule, alert_value)
|
|
if alert_log.trigger_count >= alert_rule.trigger_count:
|
|
alert_log.status = 1
|
|
push_alert(alert_log)
|
|
else:
|
|
dict_data = dict(
|
|
alert_tag=alert_tag,
|
|
alert_target_name=f"采集节点({node.node_number})",
|
|
alert_level=alert_rule.alert_level,
|
|
status=0,
|
|
trigger_count=1,
|
|
alert_content=complete_alert_text(node, alert_rule, alert_value),
|
|
)
|
|
alert_log = IotAlertLog(**dict_data)
|
|
db.session.add(alert_log)
|
|
db.session.commit()
|
|
else:
|
|
current_app.logger.info(f"节点{node.node_number}值{alert_value}未触发告警规则{alert_rule.alert_rule}")
|
|
|
|
return ""
|
|
|
|
def get_value(data_type: str, alert_value: str):
|
|
"""
|
|
获取告警值
|
|
Args:
|
|
data_type: 数据类型
|
|
alert_value: 告警值
|
|
"""
|
|
if data_type == "int":
|
|
return int(alert_value)
|
|
elif data_type == "float":
|
|
return float(alert_value)
|
|
elif data_type == "double":
|
|
return float(alert_value)
|
|
else:
|
|
return alert_value
|
|
|
|
|
|
def is_alert_trigger(alert_rule_text: str, value_data: Any):
|
|
"""
|
|
检查告警值是否满足触发规则
|
|
Args:
|
|
alert_rule_text: 告警规则文本
|
|
value_data: 告警值
|
|
"""
|
|
# 解析告警规则文本,判断是否满足触发规则
|
|
try:
|
|
result = simple_eval_expression(alert_rule_text, {'x': value_data})
|
|
return result
|
|
except (NameError, SyntaxError, TypeError) as e:
|
|
current_app.logger.error(f"评估表达式 {alert_rule_text} 时出错: {e}")
|
|
return False
|
|
|
|
def push_alert(alert_log: IotAlertLog):
|
|
"""
|
|
推送告警消息
|
|
Args:
|
|
alert_log: 告警日志
|
|
"""
|
|
|
|
level = f"{alert_log.alert_level}"
|
|
alert_push_list = db.session.scalars(select(IotAlertPush).filter(IotAlertPush.alert_level.contains(level)).filter_by(status=1)).all()
|
|
if alert_push_list:
|
|
for alert_push in alert_push_list:
|
|
# TODO 调用地址发送告警消息
|
|
current_app.logger.info(f"推送告警消息到 {alert_push.push_url},内容:{alert_log.alert_content}")
|
|
|
|
def complete_alert_text(node: IotNode, alert_rule: IotAlertRule, alert_value: str):
|
|
"""
|
|
完善告警内容文本
|
|
Args:
|
|
node: 节点
|
|
alert_rule: 告警规则
|
|
alert_value: 告警值
|
|
"""
|
|
alert_text_temp = alert_rule.alert_text or ""
|
|
alert_text_temp = alert_text_temp.replace("{_workshopName_}", node.workshop.workshop_name+"("+node.workshop.workshop_number+")")
|
|
alert_text_temp = alert_text_temp.replace("{_deviceName_}", node.device.device_name+"("+node.device.device_number+")")
|
|
alert_text_temp = alert_text_temp.replace("{_endpointName_}", node.endpoint.endpoint_name+"("+node.endpoint.endpoint_number+")")
|
|
alert_text_temp = alert_text_temp.replace("{_mark_}", node.mark)
|
|
alert_text_temp = alert_text_temp.replace("{_nodeNumber_}", node.node_number)
|
|
alert_text_temp = alert_text_temp.replace("{_alertValue_}", alert_value)
|
|
return alert_text_temp
|
|
|
|
def simple_eval_expression(expr, variables):
|
|
"""
|
|
简单地评估包含变量的字符串表达式
|
|
:param expr: 字符串表达式,如 "x > 5 and y < 10"
|
|
:param variables: 变量字典,如 {'x': 7, 'y': 3}
|
|
:return: 表达式结果
|
|
"""
|
|
return simple_eval(expr, names=variables) |