JsonLogic 基于json的规则引擎

规则引擎可以将开发者从复杂的if地狱中解脱出来,可有很多种玩法。
JsonLogic就是一种以json格式来定义规则的简易的规则引擎,有多种语言的实现,本文以Python实现来介绍。

安装

pip install json-logic-qubit
截止本文写作日前,版本是0.9.1

使用

# 导入规则引擎
from json_logic import jsonLogic, add_operation

# json规则开头的key是操作符,支持的符号请参考官网,支持多种符号嵌套
rules = {"and": [
    {"<": [{"var": "temp"}, 110]},
    {"==": [{"var": "pie.filling"}, "apple"]}
]}
data = {"temp": 100, "pie": {"filling": "apple"}}
# jsonLogic是将规则和数据同时导入内存,进行AST语法树的计算,实际上它就是一种准计算机语言的实现
result = jsonLogic(rules, data)
print("json_logic result: {}".format(str(result)))

# 测试:自定义操作符
def plus(x, y):
    return x+y

ops_plus = plus
# 将自定义的函数(即操作符号)加入到jsonlogic的语法树中,注意:符号是全局的,会覆盖原由定义
add_operation("plus", ops_plus)
result = jsonLogic({
    "plus": [2, 20]
})
print("json_logic result: {}".format(str(result)))

# 测试逻辑控制:如果是3的倍数输出fizz,5的倍数输出buzz,否则输出原值
rules = {
    "if": [
        {"==": [{"%": [{"var": "i"}, 15]}, 0]},
        "fizzbuzz",

        {"==": [{"%": [{"var": "i"}, 3]}, 0]},
        "fizz",

        {"==": [{"%": [{"var": "i"}, 5]}, 0]},
        "buzz",

        {"var": "i"}
    ]
}

for i in range(30):
    print("when i={}, return {}".format(
        str(i), 
        jsonLogic(rules, {"i": i})))

实例Demo

目标:从一堆混合的告警数据中,判断来自Zabbix的告警数量是否超过阈值

// 模拟的原始数据
{
    "data": [{
        "source": "Zabbix","no": 1}, {
        "source": "Zabbix","no": 1}, {
        "source": "Zabbix","no": 1}, {
        "source": "Zabbix","no": 1}, {
        "source": "Zabbix","no": 1}, {
        "source": "EMC","no": 1}, {
        "source": "ELK","no": 1}]
}

// 规则表达:如果zabbix记录数量>3次,触发反馈True
{
    ">": [
            {
                "reduce": [
                    {
                        "filter": [
                            { "var": "data" },
                            { "==": [{ "var": "source"}, "Zabbix"] }
                        ]
                    },
                    {"+":[{"var":"current.no"}, {"var":"accumulator"}]},
                    0
                ]
            },
            3
    ]
}

Python代码,基于Mug框架,省略部分细节

# 主控制
def exec_rule():
    '''执行规则'''
    try:
        # 数据预处理:从db中读取所有数据,组成大的json,作为data
        json_data = dict()
        all_data = rule_engine_util.get_all_data_json()
        # 预处理1,在每个data中,补充{"no": 1},作为计数器
        _new_datas = []
        for _data in all_data:
            _data['no'] = 1
            _new_datas.append(_data)
        json_data['data'] = _new_datas

        # 加载规则表
        all_rule = rule_engine_util.get_all_rule()

        # 遍历执行规则,只要有1个触发,就执行后跳出
        for _rule in all_rule:
            if rule_engine_util.exec_rule(rule=_rule, data=json_data):
                _msg = '触发规则: {}'.format(_rule.rule_name)
                return dict(code=200, message=_msg)

        return dict(code=200, message='没有触发任何规则')

    except Exception as e:
        logger.exception(e)
        abort(400, str(e))

# rule_engine_util部分
import json
from json_logic import jsonLogic

def get_all_data_json():
    '''读取所有数据的json'''
    data = []

    query = TestData.select()
    for row in query:
        data.append(json.loads(row.data_json))

    return data

def get_all_rule():
    '''读取所有规则'''
    data = []

    query = Rule.select()
    for row in query:
        data.append(row)

    return data

def exec_rule(rule, data):
    '''执行指定规则'''
    # 是否触发
    fired_flg = False

    if not isinstance(rule, Rule):
        raise TypeError("参数rule的类型错误")

    try:
        _rule = json.loads(rule.rule_json)

        result = jsonLogic(_rule, data)
        if isinstance(result, bool):
            fired_flg = result
        else:
            # TODO 可以拓展多种反馈类型,初期demo只要bool
            pass
    except Exception as e:
        logger.exception(e)
        print(e)

    return fired_flg

参考