监控告警是日志管理系统的重要功能,您可以让日志易轻松替您监控数据,我们可以把您的已存搜索按预设计划周期性执行,当满足触发条件时我们将通过指定的告警方式及时通知您。
通常的告警方式是电子邮件,不过日志易也支持syslog和HTTP转发等的其他灵活方式。
告警有两种配置方法:
一种是在搜索页面搜索完成保存为告警,然后进入告警配置页面进行告警相关配置。
一种在搜索页面搜索完成保存为新搜索,后面可以到告警模块新建告警选择已保存搜索。
(注:告警配置搜索语句中不可包含%)
目前日志易提供事件数告警、字段统计告警、连续统计告警、基线对比告警和spl告警五种告警模式。
您可以创建基于搜索结果的告警触发条件,在一个给定的时间范围内触发告警的阈值数。例如,您可以设置告警条件为5分钟内搜索结果计数超过10次(基于时间戳)。
字段统计告警为您提供针对字段内容的告警设置,在触发条件中您需要填写字段名,统计方式可以在下拉框中选择,cardinality(独立计数)、sum (求和)、avg(平均值)、max(最大值)、min(最小值)。例如,告警触发条件为:clientip在5分钟之内某个ip的计数值超过10。
连续告警为您提供连续触发告警功能,即当某个告警条件在某个时间内连续触发次数达到阀值,才触发告警。例如,告警触发条件为:apache.status在1小时之内超过404的次数超过50,则触发告警。
基线告警是将阈值设定为一个统计的基线值(随时间变动),您需要选择基线生成的时间范围,同时,基线对比告警给您提供了更灵活的触发范围设定方式——您可以在下拉框中选择大于、小于、在区间内、在区间外。例如,resp_len如果与上周的统计平均值相比,小于基线值50%或者超过基线值150%即触发告警。
用户可以针对通过spl语句建立的新字段建立告警,只需要在空格处填写正确的字段名称即可。
告警抑制:您可以设定一个固定时间段,在该时间段内触发告警之后则系统不再重复发送同类告警信息 。
倍增式时间段抑制:第一次触发告警后不再发送告警信息的时间段长度每次翻倍,直到设置的最大时长后重置 。
例如:设置为10分钟内只发送一次告警。同时选择抑制间隔时间翻倍,直到60分钟后取消抑制
您可以配置收到告警的方式,日志易支持rsyslog告警、邮件告警和告警转发三种模式。配置时可以测试运行,点击可以实际运行一次告警推送,验证推送配置。
同时你也可以按照告警插件开发规范进行自定义的告警接口开发。
选择添加rsyslog告警,用户可以根据实际情况自由更改Rsyslog地址、协议标准等内容。syslog内容可参考页面右侧说明。
您可以自行设定邮件标题、通知的邮箱地址以及告警邮件内容。系统提供默认的标准告警模板,屏幕右侧是所有可选择模板变量名称,选择对应的变量名称复制到左侧的内容模板即可,设定完成后勾选“启用该告警”,点击保存完成操作。
邮件告警采用django模板语言渲染。您可以查看日志易内置的邮件告警内容默认模板进行酌情修改。
添加能接受请求的地址,系统会发送JSON格式的告警内容到该地址。JSON的具体结构与模板基本相同,请参照下述模板。三种模式都为使用一种模板进行内容的渲染,模板可用的变量:
{ "send_time": Number, //往接收服务发送的时间点,unix时间戳。模板内是python的datetime对象 "is_alert_recovery": false // 默认不是告警恢复邮件结果 "exec_time": Number, //搜索的执行时间点,unix时间戳。模板内是python的diatomite对象。 "name": String, // 对应Web页面上告警名 "description": String, // 对应Web页面上告警的描述 "check_interval": Number, // 检测时间间隔 "search": { "name": String, // 对应Web上已存搜索的名字 "source_group": String, // 日志分组 "query": String, // query内容 "filter": String // filter内容 "extend_search_name": String, // 告警扩展搜索(可以没有)对应Web上已存搜索的名字 "extend_source_group": String, // 告警扩展搜索(可以没有)的日志分组 "extend_query": String, // 告警扩展搜索(可以没有)的query内容 "extend_filter": String // 告警扩展搜索(可以没有)的filter内容 }, "result": { "total": Number, //命中了多少条日志 "hits": [ { "appname": String, "tag": String, "hostname": String, "raw_message": String }// 一个hit只包含这四项 {// 或者当是spl的统计型的结果的时候 // hit的字段是key:value分别为用户 // eval的值 } ..... ], // 命中的日志的内容,只有count统计和spl统计才有命中的具体日志 "terms": [ { "key": String, "doc_count": Number }, ], // 字段统计告警中的独立数统计才有的结果 "columns": [ { "name": String, // 当spl时候列名的顺序是有意义的 "type": String, } ], // 当时spl统计才有的结果 "value": Number // 连续统计告警,基线告警的结果。字段统计告警里的最大最小平均值统计的结果。 "extend_total": Number, //告警扩展搜索(可以没有)命中了多少条日志,当为spell的transaction和stats型结果时候是transaction的group和stats结果有多少行,而不是与之关联的事件数。 "extend_hits": [// (可以没有) {// 普通事件结果 "appname": String, "tag": String, "raw_message": String, "hostname": String }// 当扩展搜索是spl搜索的stats结果时候,是在spl中eval出来的字段的值 ], //告警扩展搜索(可以没有)搜索出来的内容 }, "strategy": { "name": "count|field_stat|sequence_stat|baseline_cmp|spl_query",// 五种告警策略方式 "description": "事件数告警|字段统计告警|连续统计告警|基线对比告警|spl告警", // 策略中文描述,对应网页上 "trigger": { "field": String, // 字段,策略count没有此字段 "start_time": Number, // 这次告警查询的开始时间。 "end_time": Number, // 这次告警查询的结束时间。 "method": "count|cardinality|sum|avg|max|min", //统计方法,count策略的method是count "method_as_string": String, "threshold": Number, //只有连续告警统计有阈值 "baseline_base_value": Number, //只有基线告警才有,基线的百分比的100%代表的数值。 "baseline_start_time": Number, // 对照基线的时间范围开始时间 "baseline_end_time": Number, // 对照基线的时间范围结束时间 "compare": ">|<|in|ex", // 大于或小于,基线告警专有的还有in和ex "compare_style": String, // 合法值为fixed或者relative "compare_value": [ Number ] // Array[Number]比较的值,除了基线告警都是一个值比较,基线告警因为有in和ex这里会是两个值 } // 触发条件 } }
在启用状态的告警,一旦触发,会将触发告警的即时状态单独记录,供事后查询。在告警列表上点击运行趋势图,即可进入该告警的历史记录页
每条历史记录,可以有查看详情和搜索操作。告警详情浮层展现这条记录在触发时刻的触发值。点击搜索按钮,则跳转到搜索页面,打开该告警关联的已存搜索语句,并自动调整过滤时段为告警触发的开始、结束时间,您可以直接查看异常时段的事件列表或统计。
只需将插件对应的python脚本拷贝到所有yottaweb模块的/opt/rizhiyi/parcels/yottaweb/yottaweb/app/alert/plugins/ 目录.然后重启所有yottaweb服务.即可在添加告警界面看到对应可选告警类型。
插件是一个python2.7版本的脚本,由YottaWeb模块负责调用。其可import的库只有django和python标准库。日志易约定告警插件需要有一个字典变量和两个函数。
告警插件中需定义python字典变量META。是插件与Web界面配置的接口。用户在配置界面看到的配置项列表,输入的配置项内容的格式,还有最终保存在数据库中的配置项结果,都由此定义,结构为:
name: 插件名。注意不可与其他插件有重复 alias: 展示名,在Web界面上下拉菜单选择告警方式时显示的名字 configs: 配置项列表。界面上的所有配置项都是由这个configs数组指定的,显示的顺序也是这个数组里的顺序。 name: 配置项名字,不可重名 alias: 展示名,在Web界面告警配置处显示的此配置项的名字 presence: Boolean型,是否必填。将用于Web前端操作保存告警配置时候的检查项。 value_type: 此配置项值的类型,当前只支持String。 value: 配置项的值。默认无需填写,在Web界面保存配置后,会自动填写此值。 default_value: 默认值。默认值也会显示在界面上。 input_type: 输入方式类型,用于指定前端在此配置项输入时候采用何种处理。当前只支持值为email:含义是带有数据提示的用户信息中email的输入其最终结果会保存为逗号分隔的邮箱地址。 style: 配置Web界面上此配置项输入框的大小 cols: 几个字符的宽度 rows: 几个字符的高度
告警插件中可以定义两个函数handle和content来指定当告警被触发了之后的两类操作。它们的参数是一样的:
meta: 第一个参数meta的含义是不同告警经过用户配置后的META信息,是一个python的字典,字典的结构与上述的插件要求的常量词典META一致。用户写插件时,使用此信息进行自己需要的处理。 alert: 第二个参数alert的含义是告警信息本身,是一个python的字典。其结构见2.4.3节中的说明。用户使用他来获取当次告警所需的所有信息。 handle函数里实现此告警的执行操作,在告警被从frontend发送给Web执行的时候,执行的就是handle函数。函数的返回值约定如下: 返回值: 空,无返回值。可抛出异常,在外围调用处有处理会记录一条错误信息,但当异常发生时,并不会重试执行。有重试等其他可靠性需求,需在handle内自行处理。 content:函数实现在告警预览和告警历史中,对应告警如何显示告警内容。函数可抛出异常,在外部调用处有处理会记录一条错误信息,当异常发生时,告警预览和历史内看到的告警内容就为一条错误信息。函数的返回值约定如下: 返回值:String类型
以http_forwarder插件为例。
http_forwarder插件是将告警信息再次POST到一个用户配置的地址,用户需启动自己的服务,随后可用完整的告警信息对告警进行再处理。
META就是一个python的字典,并无特殊之处。只要按照上述的META格式要求写就可。
内容如下:
# -*- coding: utf-8 -*- # wu.ranbo@yottabyte.cn # 2016-05-19 # Copyright 2016 Yottabyte # filename: yottaweb/apps/alert/plugins/simple_email.py # file description: 最简单的告警,所有客户都会带着 __author__ = 'wu.ranbo' import logging import requests import json import copy req_logger = logging.getLogger("django.request") META = { "name": "http_forwarder", "version": 1, "alias": "告警转发", "configs": [ { "name": "address", "alias": "http转发地址", "presence": True, "value_type": "string", "default_value": "", "style": { "rows": 1, "cols": 30 } } ] }
content
content方法就是普通的python方法。插件接口只要求此方法的入参形式和返回值为String。此插件的content是将初始的告警信息用json格式显示出来。
def content(params, alert): return json.dumps(alert, ensure_ascii=False, indent=4).encode("utf-8", "ignore")
handle
handle内容为按照用户配置的http地址,将告警信息原文发送出去。
def handle(params, alert): try: address = params['configs'][0]['value'] requests.post(address, data=json.dumps(alert)) req_logger.debug("alert.plugs.htt_forwarder send to %s, data:%s.", address, alert) except Exception, e: req_logger.error("alert.plugins.http_forwarder got exception %s", e) raise e
为对接客户短信平台,通过客户提供的短信接口进行开发,选取http接口方式,通过httpget方式将告警信息发送至客户短信平台,由短信平台来发送短信。
【吴江农商行】2017-10-27 15:40:00apache事件数超阈值需关注,5分钟内计数92>10告警级别低,针对此告警的描述
【吴江农商行】2017-10-27 15:40:00apache.resp_len值总和超阈值需关注,apache.resp_len15分钟内总计7266000.0>10000.0告警级别低,针对此告警的描述
【吴江农商行】2017-10-27 15:40:00apache_resp_len连续超阈值需关注,apache.resp_len5分钟内达到阈值10.0次数20>10.0告警级别低,针对此告警的描述
【吴江农商行】2017-10-27 15:40:03apache_resp_len超过基线值90%需关注,apache.resp_len搜索结果10019.5170362>90.0%基线值9853.8243199告警级别低,针对此告警的描述
【吴江农商行】2017-10-27 15:40:00核心业务系统总交易成功率-业务成功率低于阈值需关注,5分钟内p_t_sucess的值96<99.0告警级别低,针对此告警的描述
#/usr/bin/env python # -*- coding: utf-8 -*- # Copyright 2016 Yottabyte # filename: yottaweb/apps/alert/plugins/sendsms-http-wjrcb.py import logging import requests import json import copy import httplib import urllib2 import sys import urllib import suds import codecs import datetime import time defaultencoding = 'utf-8' if sys.getdefaultencoding() != defaultencoding: reload(sys) sys.setdefaultencoding(defaultencoding) req_logger = logging.getLogger("django.request") META = { "name": "sendsms-http-wjrcb", "version": 1, "alias": "短信告警", "configs": [ { "mobile": "手机号", "alias": "手机号码,多个号码使用英文逗号作为分隔符", "presence": True, "value_type": "string", "default_value": "", "style": { "rows": 1, "cols": 30 } } ] } #日志记录器 logger=logging.getLogger() file=logging.FileHandler("/data/rizhiyi/logs/sms-http.log") logger.addHandler(file) formatter=logging.Formatter("%(asctime)s %(levelname)s %(message)s") file.setFormatter(formatter) logger.setLevel(logging.NOTSET) logger.info("begin to log") def datetime_to_timestamp(dt): return int(dt.strftime("%s"))*1000 + dt.microsecond/1000 def deparse_alert_post(out_alert_post): alert_post = copy.deepcopy(out_alert_post) alert_post['send_time'] = datetime_to_timestamp(alert_post['send_time']) alert_post['exec_time'] = datetime_to_timestamp(alert_post['exec_time']) trigger = alert_post['strategy']['trigger'] if 'start_time' in trigger: trigger['start_time'] = datetime_to_timestamp(trigger['start_time']) if 'end_time' in trigger: trigger['end_time'] = datetime_to_timestamp(trigger['end_time']) if 'baseline_start_time' in trigger: trigger['baseline_start_time'] = datetime_to_timestamp(trigger['baseline_start_time']) if 'baseline_end_time' in trigger: trigger['baseline_end_time'] = datetime_to_timestamp(trigger['baseline_end_time']) if 'compare_desc_text' in trigger: del trigger['compare_desc_text'] alert_post['strategy']['trigger'] = trigger del alert_post['_alert_meta'] return alert_post def sendsms(params,mobile,content): url='http://192.168.77.5:8080/cgi-bin/sendsms' msgtype='1' password='123456' username='rzy' logger.info('##############################################################') logger.info('发送短信内容:'+ content) #content编码调整,由utf-8转为unicode再转为gb2312 if(isinstance(content, str)): content = content.encode('gb2312') else: content = content.decode('utf8').encode('gb2312') #发送http get请求 payload = {'username':username, 'password':password,'to':mobile,'text':content,'msgtype':msgtype} custome = requests.get(url, params=payload) logger.info('短信发送返回状态值:' + custome.text) code = int(custome.text) #记录返回状态码到log文件中 if code == 0: logger.info('短信发送结果:正常发送') elif code == -2: logger.error('短信发送结果:发送参数填定不正确') elif code == -3: logger.error('短信发送结果:用户载入延迟') elif code == -6: logger.error('短信发送结果:密码错误')![]() elif code == -7: logger.error('短信发送结果:用户不存在') elif code == -11: logger.error('短信发送结果:发送号码数理大于最大发送数量') elif code == -12: logger.error('短信发送结果:余额不足') elif code == -99: logger.error('短信发送结果:内部处理错误') else: logger.warning('短信发送结果:未知错误') #requests.post(url, data=json.dumps(payload)) def content(params, alert): origin_alert = deparse_alert_post(alert) return json.dumps(origin_alert, ensure_ascii=False, indent=4).encode("utf-8", "ignore") def handle(params, alert): try: #logger.debug("alert:%s"%alert) #logger.debug("address:%s"%address) #logger.debug("alert.name:%s"%alert['name']) address = params['configs'][0]['value'] addressList = address.split(",") now_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') alertsendtime = alert['send_time'] alertsendtime = datetime.datetime.strftime(alertsendtime,'%Y-%m-%d %H:%M:%S') alertstarttime = alert['strategy']['trigger']['start_time'] alertstarttime = datetime.datetime.strftime(alertstarttime,'%Y-%m-%d %H:%M:%S') timestamp_starttime = time.mktime(time.strptime(alertstarttime, "%Y-%m-%d %H:%M:%S")) alertendtime = alert['strategy']['trigger']['end_time'] alertendtime = datetime.datetime.strftime(alertendtime,'%Y-%m-%d %H:%M:%S') timestamp_endtime = time.mktime(time.strptime(alertendtime, "%Y-%m-%d %H:%M:%S")) alerttimerange = int(timestamp_endtime) - int(timestamp_starttime) alerttype = alert['strategy']['name'] alertcompare = alert['strategy']['trigger']['compare'] alertcomparevalue = alert['strategy']['trigger']['compare_value'] alertlevel = alert['strategy']['trigger']['level'] if alertlevel == 'low': alertlevelname = '低' elif alertlevel == 'mid': alertlevelname = '中' elif alertlevel == 'high': alertlevelname = '高' if alerttype == 'count': eventnumber = alert['result']['total'] alertmethod = alert['strategy']['trigger']['method'] content = str(alertsendtime) + alert['name'] + "," + str(alerttimerange/60) + "分钟内计数" + str(eventnumber) + alertcompare + str(int(alertcomparevalue[0])) + "告警级别" + alertlevelname + "," + alert['description'] for mobile in addressList: sendsms(params,mobile.strip(" "),content) elif alerttype == 'field_stat': alertfield = alert['strategy']['trigger']['field'] alertmethod = alert['strategy']['trigger']['method'] if alertmethod == 'cardinality': alertmethodname = '事件数' alettresultterms = alert['result']['terms'] #alertresultlist = alettresultterms.keys() alertresultlistcount = len(alettresultterms) elif alertmethod == 'sum': alertmethodname = '总计' alertresultvalue = alert['result']['value'] elif alertmethod == 'avg': alertmethodname = '平均数' alertresultvalue = alert['result']['value'] elif alertmethod == 'max': alertmethodname = '最大数' alertresultvalue = alert['result']['value'] elif alertmethod == 'min': alertmethodname = '最小数' alertresultvalue = alert['result']['value'] if alertmethod == 'cardinality': content = str(alertsendtime) + alert['name'] + "," + alertfield + str(alerttimerange/60) + "分钟内" + str(alertmethodname) + str(alertresultlistcount)+ alertcompare + str(alertcomparevalue[0]) + "告警级别" + alertlevelname + "," + alert['description'] for mobile in addressList: sendsms(params,mobile.strip(" "),content) elif alertmethod == 'sum' or alertmethod == 'avg' or alertmethod == 'max' or alertmethod == 'min': content = str(alertsendtime) + alert['name'] + "," + alertfield + str(alerttimerange/60) + "分钟内" + str(alertmethodname) + str(alertresultvalue) + alertcompare + str(alertcomparevalue[0]) + "告警级别" + alertlevelname + "," + alert['description'] for mobile in addressList: sendsms(params,mobile.strip(" "),content) elif alerttype == 'sequence_stat': alertresultvalue = alert['result']['value'] alertfield = alert['strategy']['trigger']['field'] alertthreshold = alert['strategy']['trigger']['threshold'] content = str(alertsendtime) + alert['name'] + "," + alertfield + str(alerttimerange/60) + "分钟内达到阈值" + str(alertthreshold) + "次数" + str(alertresultvalue) + alertcompare + str(alertcomparevalue[0]) + "告警级别" + alertlevelname + "," + alert['description'] for mobile in addressList: sendsms(params,mobile.strip(" "),content) elif alerttype == 'baseline_cmp': alertresultvalue = alert['result']['value'] alertfield = alert['strategy']['trigger']['field'] alertbaseline_base_value = alert['strategy']['trigger']['baseline_base_value'] if alertcompare == '>' or alertcompare == '<': #content = str(alertsendtime) + alert['name'] + "," + alertfield + str(alerttimerange/60) + "分钟内搜索结果" + str(alertresultvalue) + alertcompare + str((alertcomparevalue[0])*100) + "%基线值" + str(alertbaseline_base_value) + "告警级别" + alertlevelname + "," + alert['description'] content = str(alertsendtime) + alert['name'] + "," + alertfield + "搜索结果" + str(alertresultvalue) + alertcompare + str((alertcomparevalue[0])*100) + "%基线值" + str(alertbaseline_base_value) + "告警级别" + alertlevelname + "," + alert['description'] for mobile in addressList: sendsms(params,mobile.strip(" "),content) elif alertcompare == 'in' or alertcompare == 'ex': #content = str(alertsendtime) + alert['name'] + "," + alertfield + str(alerttimerange/60) + "分钟内搜索结果" + str(alertresultvalue) + "在区间" + alertcompare + "(" + str((alertcomparevalue[0])*100) + "-" + str((alertcomparevalue[1])*100) + ")%基线值" + str(alertbaseline_base_value) + "告警级别" + alertlevelname + "," + alert['description'] content = str(alertsendtime) + alert['name'] + "," + alertfield + "搜索结果" + str(alertresultvalue) + "在区间" + alertcompare + "(" + str((alertcomparevalue[0])*100) + "-" + str((alertcomparevalue[1])*100) + ")%基线值" + str(alertbaseline_base_value) + "告警级别" + alertlevelname + "," + alert['description'] for mobile in addressList: sendsms(params,mobile.strip(" "),content) elif alerttype == 'spl_query': alertfield = alert['strategy']['trigger']['field'] for temphit in alert['result']['hits']: if temphit.has_key(alertfield): tempvalue = temphit[alertfield] content = str(alertsendtime) + alert['name'] + "," + str(alerttimerange/60) + "分钟内" + alertfield + "的值" + str(tempvalue) + alertcompare + str(alertcomparevalue[0]) + "告警级别" + alertlevelname + "," + alert['description'] for mobile in addressList: sendsms(params,mobile.strip(" "),content) except Exception, e: req_logger.error("alert.plugins.sendsms-http-wjrcb got exception %s", e) raise e
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!