Redian新闻
>
如何使用python发送日志易告警

如何使用python发送日志易告警

公众号新闻

现状

公司有使用日志易,对应用层监控,并实现了大屏告警。

同时也能够查询到相关的日志记录

相关的日志易查询语句如下:

#总量logtype:Nginx AND Nginx.url:\/$SubMenuName$* AND NOT `JS_CSS_JPG_FONT` | stats count() as TotalCount#错误数(大于500)logtype:Nginx AND Nginx.url:\/$SubMenuName$* AND NOT `JS_CSS_JPG_FONT` AND Nginx.status:5?? | stats count() as cnt#平均时长logtype:Nginx AND Nginx.url:\/$SubMenuName$* NOT `JS_CSS_JPG_FONT` | stats avg(Nginx.request_time) as avg_request_time | eval avg_request_time=if( avg_request_time==null,0,format("%.3f",avg_request_time))#独立访客logtype:Nginx AND Nginx.url:\/$SubMenuName$* AND Nginx.domainname:* AND NOT (Nginx.domainname:"" OR Nginx.domainname"-" OR tag:api) | where len(Nginx.domainname)<=11 | stats dc(Nginx.domainname) as cnt#最大响应时长logtype:Nginx AND Nginx.url:\/$SubMenuName$* NOT `JS_CSS_JPG_FONT` | stats max(Nginx.request_time) as max_request_time | eval max_request_time=if( max_request_time==null,0,format("%.3f",max_request_time))#最小响应时长logtype:Nginx AND Nginx.url:\/$SubMenuName$* NOT `JS_CSS_JPG_FONT` | stats min(Nginx.request_time) as min_request_time | eval min_request_time=if( min_request_time==null,0,format("%.3f",min_request_time))#错误数(4??)logtype:Nginx AND Nginx.url:\/$SubMenuName$* AND NOT `JS_CSS_JPG_FONT` AND (Nginx.status:>401 AND Nginx.status:<500) | stats count() as cnt

但是现在比较操蛋的是,日志易因为种种原因,不能开放给我们使用,我们只有查看制度权限,只能看到图表,搜索到日志,但是没有告警发送,邮件汇总等信息,十分不方便。现在想用python爬取日志易的信息,通过邮件发送,同时发送钉钉告警

思路

1、通过python的request模块,调取日志易的API,形成表格,通过邮件发送报表

2、通过python读取日志易API,查询到5XX错误,通过钉钉发送告警

3、通过python直接请求接口,返回5XX错误,通过钉钉发送告警

邮件报表发送

配置文件

录了邮件地址,日志易密码,接口url等信息

config.py

#日志易的域标识,就是iduri="http://10.136.1.88:8080/v1/"token="7a84d69cfa25b056ba9ff15fee5f32d2"#账户username="xxxxx"#密码passwd="xxxxx"
#系统名"""应用模块 智能工单 sharedWorkOrder移动门户 mbworkordertest会议室预定 WSS\/meeting打印归档 archivesManager移动入职 newEmployeeEntry小助手 sharedHelper工资单咨询 mbSharedOrderPortal\/user?type=payrollCounseling工资单重发 mbSharedOrderPortal\/user?type=payrollReissue行政报修0 mbSharedOrderPortal\/user?type=adminFacilityRepairs员工信息变更 mbSharedOrderPortal\/user?type=empMsgUpdate移动端我的订单 mbSharedOrderPortal\/user?type=myOrder新员工入职系统 Default\/NewEmployeelogin数据平台生产地址 离职管理 employeeResign模板管理 templateManager台账 ledgerManageriam iam消息平台 msgServicegetFileArchiveStatus异常
访问脚本 总量 logtype:Nginx AND Nginx.url:\/$SubMenuName$* AND NOT `JS_CSS_JPG_FONT` | stats count() as TotalCount错误数(大于500) logtype:Nginx AND Nginx.url:\/$SubMenuName$* AND NOT `JS_CSS_JPG_FONT` AND NOT ( Nginx.status:2?? OR Nginx.status:3?? OR Nginx.status:1??) | stats count() as cnt平均时长 logtype:Nginx AND Nginx.url:\/$SubMenuName$* NOT `JS_CSS_JPG_FONT` | stats avg(Nginx.request_time) as avg_request_time | eval avg_request_time=if( avg_request_time==null,0,format("%.3f",avg_request_time))独立访客 logtype:Nginx AND Nginx.url:\/$SubMenuName$* AND Nginx.domainname:* AND NOT (Nginx.domainname:"" OR Nginx.domainname"-" OR tag:api) | where len(Nginx.domainname)<=11 | stats dc(Nginx.domainname) as cnt最大响应时长 logtype:Nginx AND Nginx.url:\/$SubMenuName$* NOT `JS_CSS_JPG_FONT` | stats max(Nginx.request_time) as max_request_time | eval max_request_time=if( max_request_time==null,0,format("%.3f",max_request_time))最小相应时长 logtype:Nginx AND Nginx.url:\/$SubMenuName$* NOT `JS_CSS_JPG_FONT` | stats min(Nginx.request_time) as min_request_time | eval min_request_time=if( min_request_time==null,0,format("%.3f",min_request_time))错误数(4??) logtype:Nginx AND Nginx.url:\/$SubMenuName$* AND NOT `JS_CSS_JPG_FONT` AND (Nginx.status:>401 AND Nginx.status:<500) | stats count() as cnt
"""SysNameList={"智能工单":"sharedWorkOrder","移动门户":"mbworkordertest","会议室预定":"WSS\/meeting","打印归档":"archivesManager","移动入职":"newEmployeeEntry","小助手":"sharedHelper","工资单查询":"mbSharedOrderPortal\/user?type=payrollCounseling","工资单重发":"mbSharedOrderPortal\/user?type=payrollReissue","行政报修":"mbSharedOrderPortal\/user?type=adminFacilityRepairs","员工信息变更":"mbSharedOrderPortal\/user?type=empMsgUpdate","移动端我的订单":"mbSharedOrderPortal\/user?type=myOrder","新员工入职":"Default\/NewEmployeelogin","离职管理":"employeeResign","模板管理":"templateManager","台账":"ledgerManager","IAM":"am","消息平台":"msgService","newEmployeeUtil":"newEmployeeUtil","ledgerManager":"ledgerManager","customerService":"customerService","psaDataUtil":"psaDataUtil","account":"account","sla":"sla","mbworkorder":"mbworkorder","messageService":"messageService",}
#邮箱配置host_server = 'xxxxx'port='465'sender = 'xxxxxx' pwd = 'xxxxx1' #邮箱密码receiver = ['xxxxxxxx',]

脚本主程序

main.py,定时每天早上9:30执行

import requestsimport configimport jsonimport loggingimport osimport sysimport refrom datetime import datetimeimport timeimport pandas as pdimport openpyxlfrom openpyxl.styles import Font,Colorimport smtplib, sslfrom email.mime.text import MIMEText #邮件正文from email.mime.multipart import MIMEMultipart #邮件主体from email.header import Header #邮件头,标题、收件人等from email.mime.text import MIMETextfrom email.mime.multipart import MIMEMultipartfrom email.mime.base import MIMEBasefrom email import encoders

#查询日志易API#增加表格样式标记#version 2.0#yangchao
"""获取信息1、获取总量2、错误数(大于500)3、平均时长4、独立访客5、最大相应时长6、最小相应时长7、错误数(4??)
"""
#配置日志abspath=os.path.abspath(sys.argv[0])current_path=os.path.dirname(abspath)logging.basicConfig(filename=current_path+'/info.log',encoding='utf-8',level=logging.INFO,format='%(asctime)s - %(levelname)s - %(message)s')
#格式化时间time_now=datetime.now()format_date=time_now.strftime("%Y-%m-%d")

class Get_Api(): """ 获取api信息 """ def __init__(self) -> None: """ 初始化信息 """ self.uri=config.uri self.token=config.token self.username=config.username self.passwd=config.passwd self.url=str(self.uri+self.token+"/"+self.username+"/spl/search") #请求头 self.header={'Content-Type': 'application/json'} #Http的base认证 self.auth=requests.auth.HTTPBasicAuth(self.username,self.passwd)
#基本查询参数 #任务名 self.task_name={"task_name":"my_search"} #时间范围,一天前 self.time_range={"time_range":"-1d,now"} #从首页显示 self.page={"page":0} #分类 self.category={"category":"search"} #查询语句 self.query={"query":""} #构建查询参数字典 self.params={} self.params.update(self.task_name) self.params.update(self.page) self.params.update(self.category) self.params.update(self.time_range) #base认证 self.credentials=requests.auth.HTTPBasicAuth(self.username,self.passwd)
def GetTotal(self,sysname:str)->int: """ 获取总量 sysname:系统名 """ sql="logtype:Nginx AND Nginx.url:\/" +sysname+ "* AND NOT `JS_CSS_JPG_FONT` | stats count() as cnt" self.query["query"]=sql self.params.update(self.query)
try: respson=requests.get(self.url,params=self.params,auth=self.credentials,headers=self.header) # respson=requests.get(query_url,auth=credentials,headers=self.header) content=respson.text if respson.status_code ==200 and "error_code" not in content: logging.info("查询总量成功") elif respson.status_code !=200 and "error_code" in content: logging.error("查询总量失败%s:",content) else: logging.error("查询总量失败%s:",content) except Exception as e: logging.error("查询总量失败%s:",e) raise Exception("查询总量失败%s:",e) content_dict=json.loads(content) #pprint.pprint(content_dict) try: cnt=content_dict["result"]["sheets"]["rows"][0]["cnt"] print(cnt) except Exception as e: logging.error("转换查询总量失败:%s",e) raise Exception("转换查询总量失败:%s",e) info=sysname+":"+str(cnt) logging.info("转换查询总量成功:%s",info) return int(cnt) def GetErr5xx(self,sysname:str)->int: """ 获取错误数大于500 计算错误占比 sysname:系统名 """ sql="logtype:Nginx AND Nginx.url:\/" +sysname+"* AND NOT `JS_CSS_JPG_FONT` AND Nginx.status:5?? | stats count() as cnt" self.query["query"]=sql self.params.update(self.query) try: respson=requests.get(self.url,params=self.params,auth=self.credentials,headers=self.header) content=respson.text if respson.status_code ==200 and "error_code" not in content: logging.info("查询5xx成功") elif respson.status_code !=200 and "error_code" in content: logging.error("查询5XX失败%s:",content) else: logging.error("查询5XX失败%s:",content) except Exception as e: logging.error("查询5XX失败%s:",e) raise Exception("查询5XX失败%s:",e) content_dict=json.loads(content) #pprint.pprint(content_dict) try: cnt=content_dict["result"]["sheets"]["rows"][0]["cnt"] print(cnt) except Exception as e: logging.error("转换查询5XX失败:%s",e) raise Exception("转换查询5XX失败:%s",e) info=sysname+":"+str(cnt) logging.info("转换查询5XX成功:%s",info) return int(cnt)
def GetAvgResp(self,sysname:str)->float: """ 获取平均相应时长 sysname:系统名 """ sql="logtype:Nginx AND Nginx.url:\/"+sysname+'* NOT `JS_CSS_JPG_FONT` | stats avg(Nginx.request_time) as avg_request_time | eval avg_request_time=if( avg_request_time==null,0,format("%.3f",avg_request_time))'
self.query["query"]=sql self.params.update(self.query) try: respson=requests.get(self.url,params=self.params,auth=self.credentials,headers=self.header) content=respson.text if respson.status_code ==200 and "error_code" not in content: logging.info("查询平均时长成功") elif respson.status_code !=200 and "error_code" in content: logging.error("查询平均时长失败%s:",content) else: logging.error("查询平均时长失败%s:",content) except Exception as e: logging.error("查询平均时长失败%s:",e) raise Exception("查询平均时长失败%s:",e) content_dict=json.loads(content) #pprint.pprint(content_dict) try: cnt=content_dict["result"]["sheets"]["rows"][0]["avg_request_time"] print(cnt) except Exception as e: logging.error("转换查询平均时长失败:%s",e) raise Exception("转换查询平均时长失败:%s",e) info=sysname+":"+str(cnt) logging.info("转换查询平均时长成功:%s",info) return float(cnt)
def GetPv(self,sysname:str)->int: """ 获取独立访问数 sysname:系统名 """ sql="logtype:Nginx AND Nginx.url:\/"+sysname+'* AND Nginx.domainname:* AND NOT (Nginx.domainname:"" OR Nginx.domainname"-" OR tag:api) | where len(Nginx.domainname)<=11 | stats dc(Nginx.domainname) as cnt' self.query["query"]=sql self.params.update(self.query) try: respson=requests.get(self.url,params=self.params,auth=self.credentials,headers=self.header) content=respson.text if respson.status_code ==200 and "error_code" not in content: logging.info("查询pv成功") elif respson.status_code !=200 and "error_code" in content: logging.error("查询pv失败%s:",content) else: logging.error("查询pv失败%s:",content) except Exception as e: logging.error("查询pv失败%s:",e) raise Exception("查询pv失败%s:",e) content_dict=json.loads(content) #pprint.pprint(content_dict) try: cnt=content_dict["result"]["sheets"]["rows"][0]["cnt"] print(cnt) except Exception as e: logging.error("转换查询pv失败:%s",e) raise Exception("转换查询pv失败:%s",e) info=sysname+":"+str(cnt) logging.info("转换查询pv成功:%s",info) return int(cnt) def GetMaxResp(self,sysname:str)->float: """ 获取最大相应时长 sysname:系统名 """ sql="logtype:Nginx AND Nginx.url:\/"+sysname+'* NOT `JS_CSS_JPG_FONT` | stats max(Nginx.request_time) as max_request_time | eval max_request_time=if( max_request_time==null,0,format("%.3f",max_request_time))' self.query["query"]=sql self.params.update(self.query) try: respson=requests.get(self.url,params=self.params,auth=self.credentials,headers=self.header) content=respson.text if respson.status_code ==200 and "error_code" not in content: logging.info("查询最大时长成功") elif respson.status_code !=200 and "error_code" in content: logging.error("查询最大时长失败%s:",content) else: logging.error("查询最大时长失败%s:",content) except Exception as e: logging.error("查询最大时长失败%s:",e) raise Exception("查询最大时长失败%s:",e) content_dict=json.loads(content) #pprint.pprint(content_dict) try: cnt=content_dict["result"]["sheets"]["rows"][0]["max_request_time"] print(cnt) except Exception as e: logging.error("转换查询最大时长失败:%s",e) raise Exception("转换查询最大时长失败:%s",e) info=sysname+":"+str(cnt) logging.info("转换查询最大时长成功:%s",info) return float(cnt)

def GetMinResp(self,sysname:str)->float: """ 获取最小相应时长 sysname:系统名 """ sql="logtype:Nginx AND Nginx.url:\/"+sysname+'* NOT `JS_CSS_JPG_FONT` | stats min(Nginx.request_time) as min_request_time | eval min_request_time=if( min_request_time==null,0,format("%.3f",min_request_time))' self.query["query"]=sql self.params.update(self.query) try: respson=requests.get(self.url,params=self.params,auth=self.credentials,headers=self.header) content=respson.text if respson.status_code ==200 and "error_code" not in content: logging.info("查询最小时长成功") elif respson.status_code !=200 and "error_code" in content: logging.error("查询最小时长失败%s:",content) else: logging.error("查询最小时长失败%s:",content) except Exception as e: logging.error("查询最小时长失败%s:",e) raise Exception("查询最小时长失败%s:",e) content_dict=json.loads(content) #pprint.pprint(content_dict) try: cnt=content_dict["result"]["sheets"]["rows"][0]["min_request_time"] print(cnt) except Exception as e: logging.error("转换查询最小时长失败:%s",e) raise Exception("转换查询最小时长失败:%s",e) info=sysname+":"+str(cnt) logging.info("转换查询最小时长成功:%s",info) return float(cnt) def GetErr4xx(self,sysname:str)->int: """ 获取最大相应时长 sysname:系统名 """ sql="logtype:Nginx AND Nginx.url:\/"+sysname+'* AND NOT `JS_CSS_JPG_FONT` AND (Nginx.status:>401 AND Nginx.status:<500) | stats count() as cnt' self.query["query"]=sql self.params.update(self.query) try: respson=requests.get(self.url,params=self.params,auth=self.credentials,headers=self.header) content=respson.text if respson.status_code ==200 and "error_code" not in content: logging.info("查询4xx成功") elif respson.status_code !=200 and "error_code" in content: logging.error("查询4XX失败%s:",content) else: logging.error("查询4XX失败%s:",content) except Exception as e: logging.error("查询4XX失败%s:",e) raise Exception("查询4XX失败%s:",e) content_dict=json.loads(content) #pprint.pprint(content_dict) try: cnt=content_dict["result"]["sheets"]["rows"][0]["cnt"] print(cnt) except Exception as e: logging.error("转换查询4XX失败:%s",e) raise Exception("转换查询4XX失败:%s",e) info=sysname+":"+str(cnt) logging.info("转换查询4XX成功:%s",info) return int(cnt)

class Process_Msg(): """ 1、转换字典为表格 2、转换字典为html 3、通过邮件发送html格式邮件和表格 4、数值计算 """ pass def __init__(self,host_server,port,sender,pwd,receiver) -> None: #self.excel=None self.html=None self.new_html=None self.host_server=host_server self.port=port self.sender=sender self.pwd=pwd self.receiver=receiver self.file=None
def process_msg(self,data1:dict,data2:dict,data3:dict,data4:dict,data5:dict,data6:dict,data7:dict,data8:dict,data9:dict,data10:dict): """ 转换为表格 """ data_dict={} try: data_dict["二级系统"]=data1 data_dict["请求数"]=data2 data_dict["平均响应时间"]=data3 data_dict["最大响应时间"]=data4 data_dict["最小响应时间"]=data5 data_dict["用户数"]=data6 data_dict["请求错误数(大于500)"]=data7 data_dict["错误占比(500)"]=data8 data_dict["请求错误数(大于401)"]=data9 data_dict["错误占比(大于401)"]=data10 df=pd.DataFrame.from_dict(data_dict,orient='index') #把字典转换为dict df=df.T #行列转换 self.file=str(current_path+"/report"+format_date+".xlsx") #self.file=str(".report"+format_date+".xlsx") #self.html=str("report"+format_date+".html") self.html=str(current_path+"/report"+format_date+".html") if os.path.exists(self.file): os.remove(self.file) with pd.ExcelWriter(self.file,engine="openpyxl") as w: #写入excel表格 df.to_excel(w,sheet_name="一天统计",index="SN") #保存到sheet #df.to_excel(self.excel,sheet_name="一天统计",index="SN") df.to_html(self.html,index=False) #self.html=df.to_html(index=False) #self.html=re.sub('\n+','',self.html) except Exception as e: logging.error("转换数据失败%s",e) raise Exception("数据转换失败%s",e) if self.file or self.html: logging.info("转换数据成功,生成excel和html!") return self.file,self.html def change_color(self) ->None: #标记表格颜色 wb=openpyxl.load_workbook(self.file) wbsheet=wb["一天统计"] for row in wbsheet.iter_rows(min_row=2,max_row=wbsheet.max_row,min_col=4,max_col=4): for cell in row: if float(cell.value) >= 3.000: cell.font= Font(color=Color(rgb="FF0000")) for row in wbsheet.iter_rows(min_row=2,max_row=wbsheet.max_row,min_col=5,max_col=5): for cell in row: if float(cell.value) >= 3.000: cell.font= Font(color=Color(rgb="FF0000")) for row in wbsheet.iter_rows(min_row=2,max_row=wbsheet.max_row,min_col=9,max_col=9): for cell in row: if type(cell.value)==str: value=float(cell.value.strip("%")) if value >=10.000: cell.font= Font(color=Color(rgb="FF0000")) else: value=float(cell.value) if value >=10.000: cell.font=Font(color=Color(rgb="FF0000")) for row in wbsheet.iter_rows(min_row=2,max_row=wbsheet.max_row,min_col=11,max_col=11): for cell in row: if type(value)==str: value=float(cell.value.strip("%")) if value >=10.000: cell.font= Font(color=Color(rgb="FFFF00")) else: value=float(value) if value >=10.000: cell.font= Font(color=Color(rgb="FFFF00")) wb.save(filename=self.file) #改变html样式 self.new_html=str(current_path+"/report-"+format_date+".html") result_list=[] with open (self.html,"r",encoding="utf-8") as f: lines=f.readlines() for line in lines: if "<td>" in line: metric=(str(line.strip()).lstrip("<td>")).rstrip("</td>") if "." in metric: if "%" not in metric: if float(metric) >= 3.000: newline='<td style="background-color:rgb(255,0,0)">'+metric+'</td>\n' result_list.append(newline) else: result_list.append(line) elif "%" in metric: metric=metric.strip('%') if float(metric) >= 10.000: newline='<td style="background-color:rgb(255,0,0)">'+metric+'%</td>\n' result_list.append(newline) else: result_list.append(line) else: result_list.append(line) else: result_list.append(line) else: result_list.append(line) #删除旧的html if os.path.exists(self.html): os.remove(self.html) with open(self.new_html,"w",encoding="utf-8") as file: for line in result_list: file.write(line) def send_msg(self)->None: """ 通过邮件发送报表 """ with open(self.new_html,"r",encoding="utf-8") as f : html_content=f.read() mail_content="<h1>昨天报表情况</h1><p>以下是报表信息</p>"+html_content+"<p>详细见附件</p>"\ +"<h2>模块信息</h2>"\ +"<p>智能工单 sharedWorkOrder </p>"\ +"<p>移动门户 mbworkordertest </p>"\ +"<p>会议室预定 WSS\/meeting </p>"\ +"<p>打印归档 archivesManager </p>"\ +"<p>移动入职 newEmployeeEntry </p>"\ +"<p>小助手 sharedHelper </p>"\ +"<p>工资单咨询 mbSharedOrderPortal\/user?type=payrollCounseling </p>"\ +"<p>工资单重发 mbSharedOrderPortal\/user?type=payrollReissue </p>"\ +"<p>行政报修0 mbSharedOrderPortal\/user?type=adminFacilityRepairs </p>"\ +"<p>员工信息变更 mbSharedOrderPortal\/user?type=empMsgUpdate </p>"\ +"<p>移动端我的订单 mbSharedOrderPortal\/user?type=myOrder </p>"\ +"<p>新员工入职系统 Default\/NewEmployeelogin </p>"\ +"<p>离职管理 employeeResign </p>"\ +"<p>模板管理 templateManager </p>"\ +"<p>台账 ledgerManager </p>"\ +"<p>iam iam </p>"\ +"<p>消息平台 msgService </p>"\ +"<p>newEmployeeUtil newEmployeeUtil </p>"\ +"<p>ledgerManager ledgerManager </p>"\ +"<p>customerService customerService </p>"\ +"<p>psaDataUtil psaDataUtil </p>"\ +"<p>账单 account </p>"\ +"<p>工单 sla </p>"\ +"<p>模板 mbworkorder </p>"\ +"<p>messageService messageService </p>"\ +"<h2>查询语句</h2>"\ +"<h3>总量</h3>"\ +'<p>logtype:Nginx AND Nginx.url:\/$SubMenuName$* AND NOT `JS_CSS_JPG_FONT` | stats count() as TotalCount</p>'\ +'<h3>错误数(大于500)</h3>'\ +'<p>logtype:Nginx AND Nginx.url:\/$SubMenuName$* AND NOT `JS_CSS_JPG_FONT` AND Nginx.status:5?? | stats count() as cnt</p>'\ +'<h3>平均时长</h3>' \ +'<p>logtype:Nginx AND Nginx.url:\/$SubMenuName$* NOT `JS_CSS_JPG_FONT` | stats avg(Nginx.request_time) as avg_request_time | eval avg_request_time=if( avg_request_time==null,0,format("%.3f",avg_request_time))</p>'\ +'<h3>独立访客</h3>'\ +'<p>logtype:Nginx AND Nginx.url:\/$SubMenuName$* AND Nginx.domainname:* AND NOT (Nginx.domainname:"" OR Nginx.domainname"-" OR tag:api) | where len(Nginx.domainname)<=11 | stats dc(Nginx.domainname) as cnt</p>'\ +'<h3>最大响应时长</h3>'\ +'<p>logtype:Nginx AND Nginx.url:\/$SubMenuName$* NOT `JS_CSS_JPG_FONT` | stats max(Nginx.request_time) as max_request_time | eval max_request_time=if( max_request_time==null,0,format("%.3f",max_request_time))</p>'\ +'<h3>最小响应时长</h3>'\ +'<p>logtype:Nginx AND Nginx.url:\/$SubMenuName$* NOT `JS_CSS_JPG_FONT` | stats min(Nginx.request_time) as min_request_time | eval min_request_time=if( min_request_time==null,0,format("%.3f",min_request_time))</p>'\ +'<h3>错误数(4??)</h3>'\ +'<p>logtype:Nginx AND Nginx.url:\/$SubMenuName$* AND NOT `JS_CSS_JPG_FONT` AND (Nginx.status:>401 AND Nginx.status:<500) | stats count() as cnt'\ +'<p>请登录日志易平台详细查询http://10.136.1.88/auth/login/?SessionExpired=true&RefererUrl=%2Fdashboard%2F56%2F105%2F</p>' mail_title="日志易站点可用性自动化报表发送" #发送多个收件人 msg=MIMEMultipart() msg["From"]=self.sender msg["To"]=','.join(self.receiver) msg["Subject"]=mail_title #添加邮件内容 msg.attach(MIMEText(mail_content,'html','utf-8')) #添加邮件附件 send_file=open(self.file,'rb') part = MIMEBase('application', 'octet-stream') part.set_payload(send_file.read()) encoders.encode_base64(part) part.add_header('Content-Disposition', "attachment; filename= %s" % self.file) msg.attach(part) send_file.close()

#建立连接并发送邮件 try: """ with smtplib.SMTP(self.host_server,self.port) as server: server.starttls() server.login(self.sender,self.pwd) server.send_message(msg) """ server=smtplib.SMTP_SSL(self.host_server,self.port) server.login(self.sender,self.pwd) server.sendmail(self.sender,self.receiver,msg.as_string()) server.quit() except Exception as e: if e: logging.error("邮件发送失败:%s",e) raise Exception("邮件发送失败%s:",e) logging.info("邮件发送成功")
#删除文件 if os.path.exists(self.new_html): os.remove(self.new_html) if os.path.exists(self.file): os.remove(self.file)


if __name__=="__main__": start_time=time.time() #总量列表 TotalList=[] #错误数大于500 Err5xxList=[] #平均时长 AvgRespList=[] #独立访问 PvList=[] #最大响应时长 MaxRespList=[] #最小响应时长 MinRespList=[] #错误数4XX Err4xxList=[] #错误占比5XX Err5xxRateList=[] #错误占比4XX Err4xxRateList=[] #系统名称 SysNameList=[] GetApi=Get_Api() logging.info("程序开始运行%s",format_date) #循环字典,读取所有系统指标 for name,sysname in config.SysNameList.items(): Total=(GetApi.GetTotal(sysname)) if Total==0: TotalList.append(0) Err5xxRateList.append(0) Err4xxRateList.append(0) Err5xxList.append(0) Err4xxList.append(0) elif Total !=0: Err5xx=(GetApi.GetErr5xx(sysname)) Err4xx=(GetApi.GetErr4xx(sysname)) try: #计算5xx错误占比 Err5xxRate="{:.3f}".format((Err5xx/Total)*100) Err5xxRate=Err5xxRate+"%" except Exception as e: logging.error("转换查询5XX占比失败:%s",e) raise Exception("转换查询5XX占比失败:%s",e) logging.info("转换查询5XX占比成功:%s:%s",sysname,Err5xxRate) #计算4xx错误占比 try: Err4xxRate="{:.3f}".format((Err4xx/Total)*100) Err4xxRate=Err4xxRate+"%" except Exception as e: logging.error("转换查询5XX占比失败:%s",e) raise Exception("转换查询5XX占比失败:%s",e) logging.info("转换查询4XX占比成功:%s:%s",sysname,Err4xxRate) TotalList.append(Total) Err5xxRateList.append(Err5xxRate) Err4xxRateList.append(Err4xxRate) Err5xxList.append(Err5xx) Err4xxList.append(Err4xx)
AvgRespList.append(GetApi.GetAvgResp(sysname=sysname)) PvList.append(GetApi.GetPv(sysname)) MaxRespList.append(GetApi.GetMaxResp(sysname)) MinRespList.append(GetApi.GetMinResp(sysname)) SysNameList.append(name) ProcessMsg=Process_Msg(host_server=config.host_server, port=config.port, sender=config.sender, pwd=config.pwd, receiver=config.receiver, ) ProcessMsg.process_msg(data1=SysNameList,data2=TotalList,data3=AvgRespList, data4=MaxRespList,data5=MinRespList,data6=PvList,data7=Err5xxList, data8=Err5xxRateList,data9=Err4xxList,data10=Err4xxRateList, ) ProcessMsg.change_color() ProcessMsg.send_msg() end_time=time.time()
logging.info("程序结束运行,共耗时%s秒",str(end_time-start_time))

使用效果

钉钉告警

配置文件

#日志易的域标识,就是idrizhiyi_uri="http://10.136.1.88:8080/v1/"rizhiyi_token="7a84d69cfa25b056ba9ff15fee5f32d2"#账户rizhiyi_username="xxxxxx"#密码rizhiyi_passwd="xxxxxx"
#系统名"""应用模块 智能工单 sharedWorkOrder移动门户 mbworkordertest会议室预定 WSS\/meeting打印归档 archivesManager移动入职 newEmployeeEntry小助手 sharedHelper工资单咨询 mbSharedOrderPortal\/user?type=payrollCounseling工资单重发 mbSharedOrderPortal\/user?type=payrollReissue行政报修0 mbSharedOrderPortal\/user?type=adminFacilityRepairs员工信息变更 mbSharedOrderPortal\/user?type=empMsgUpdate移动端我的订单 mbSharedOrderPortal\/user?type=myOrder新员工入职系统 Default\/NewEmployeelogin数据平台生产地址 离职管理 employeeResign模板管理 templateManager台账 ledgerManageriam iam消息平台 msgServicegetFileArchiveStatus异常
访问脚本 总量 logtype:Nginx AND Nginx.url:\/$SubMenuName$* AND NOT `JS_CSS_JPG_FONT` | stats count() as TotalCount错误数(大于500) logtype:Nginx AND Nginx.url:\/$SubMenuName$* AND NOT `JS_CSS_JPG_FONT` AND NOT ( Nginx.status:2?? OR Nginx.status:3?? OR Nginx.status:1??) | stats count() as cnt平均时长 logtype:Nginx AND Nginx.url:\/$SubMenuName$* NOT `JS_CSS_JPG_FONT` | stats avg(Nginx.request_time) as avg_request_time | eval avg_request_time=if( avg_request_time==null,0,format("%.3f",avg_request_time))独立访客 logtype:Nginx AND Nginx.url:\/$SubMenuName$* AND Nginx.domainname:* AND NOT (Nginx.domainname:"" OR Nginx.domainname"-" OR tag:api) | where len(Nginx.domainname)<=11 | stats dc(Nginx.domainname) as cnt最大响应时长 logtype:Nginx AND Nginx.url:\/$SubMenuName$* NOT `JS_CSS_JPG_FONT` | stats max(Nginx.request_time) as max_request_time | eval max_request_time=if( max_request_time==null,0,format("%.3f",max_request_time))最小相应时长 logtype:Nginx AND Nginx.url:\/$SubMenuName$* NOT `JS_CSS_JPG_FONT` | stats min(Nginx.request_time) as min_request_time | eval min_request_time=if( min_request_time==null,0,format("%.3f",min_request_time))错误数(4??) logtype:Nginx AND Nginx.url:\/$SubMenuName$* AND NOT `JS_CSS_JPG_FONT` AND (Nginx.status:>401 AND Nginx.status:<500) | stats count() as cnt
"""SysNameList={#"智能工单":"sharedWorkOrder","移动门户":"mbworkordertest","会议室预定":"WSS\/meeting","打印归档":"archivesManager","移动入职":"newEmployeeEntry","小助手":"sharedHelper","工资单查询":"mbSharedOrderPortal\/user?type=payrollCounseling","工资单重发":"mbSharedOrderPortal\/user?type=payrollReissue","行政报修":"mbSharedOrderPortal\/user?type=adminFacilityRepairs","员工信息变更":"mbSharedOrderPortal\/user?type=empMsgUpdate","移动端我的订单":"mbSharedOrderPortal\/user?type=myOrder","新员工入职":"Default\/NewEmployeelogin","离职管理":"employeeResign","模板管理":"templateManager","台账":"ledgerManager","IAM":"am","消息平台":"msgService","newEmployeeUtil":"newEmployeeUtil","customerService":"customerService","psaDataUtil":"psaDataUtil","account":"account","sla":"sla","mbworkorder":"mbworkorder","messageService":"messageService","法大大offer":"fdd-off","法大大入职":"fdd-entry"}
#邮箱配置mail_host_server = 'xxxxxx'mail_port='465'mail_sender = 'xxxxx' mail_pwd = 'xxxxx' #邮箱密码mail_receiver = ['xxxxxx,]
#接口字典#只用用curl命令检查api接口路径api_interface_dict={ "account_login":"https://ipsapro.isoftstone.com/account/apiAccount/security/login", #账单 "sla_login":"https://ipsapro.isoftstone.com/sla/security/login", #工单 "sharedWorkOrder":"https://ipsapro.isoftstone.com/sharedWorkOrder/order/order_add", #智能工单 "archivesManage":"https://ipsapro.isoftstone.com/archivesManager/archivesManager/api", #打印归档 "newEmployeeEntry":"http://i.isoftstone.com/newEmployeeEntry/api", #移动入职 "mbSharedOrderPortal\/user?type=payrollCounseling":"http://i.isoftstone.com/sidc/mbSharedOrderPortal/user?type=payrollCounseling", #工资单查询 "mbSharedOrderPortal\/user?type=payrollReissue":"http://i.isoftstone.com/sidc/mbSharedOrderPortal/user?type=payrollReissue", #工资单重发 "mbSharedOrderPortal\/user?type=adminFacilityRepairs":"http://i.isoftstone.com/sidc/mbSharedOrderPortal/mbSharedOrderPortal/user?type=adminFacilityRepairs", #行政保修 "mbSharedOrderPortal\/user?type=empMsgUpdate":"http://i.isoftstone.com/sidc/mbSharedOrderPortal/user?type=empMsgUpdate" ,#员工信息变更 "mbSharedOrderPortal\/user?type=myOrder":"http://i.isoftstone.com/sidc/mbSharedOrderPortal/user?type=myOrder",#移动端我的订单 "employeeResign":"http://i.isoftstone.com/employeeResign/api",#离职管理 "ledgerManager":"https://ipsapro.isoftstone.com/ledgerManager/api",#台账管理 "msgService":"https://ipsapro.isoftstone.com/msgService/api",#消息平台 "ledgerManager":"https://ipsapro.isoftstone.com/ledgerManager/api", }##账单系统接口#account_login="https://ipsapro.isoftstone.com/account/apiAccount/security/login"#account_loginPwd= "xxxxxx"#account_userName= "admin"#account_sendOrderUrl="https://ipsapro.isoftstone.com/account/apiAccount/anonymousWeb/synchronizeOrder"#account_sendWorkOrderUrl="https://ipsapro.isoftstone.com/account/apiAccount/anonymousWeb/synchronizeOrderWorker"#account_sendWorkOrderInputIUrl="https://ipsapro.isoftstone.com/account/apiAccount/anonymousWeb/synchronizeOrderWorkerInputInfoaccount"
##工单系统接口#sla_loginPwd= "xxxxxx"#sla_userName= "admin"#sla_login="https://ipsapro.isoftstone.com/sla/security/login"#sla_sendCreateOrderUrl="https://ipsapro.isoftstone.com/sla/slaAnonymousWeb/synchronizeOrder"#sla_sendCreateWorkOrderUrl="https://ipsapro.isoftstone.com/sla/slaAnonymousWeb/synchronizeOrderWorker"#sla_sendChangeStatusOrderUrl="https://ipsapro.isoftstone.com/sla/slaAnonymousWeb/synchronizeOrderStatusChange"#sla_sendChangeStatusWorkOrderUrl="https://ipsapro.isoftstone.com/sla/slaAnonymousWeb/synchronizeOrderWorkerStatusChange"#sla_orderChangeUrl="https://ipsapro.isoftstone.com/sla/slaAnonymousWeb/synchronizeOrderDeadlineDateChange"


#钉钉接口,发送告警用dingding_token="https:/xxxxxxxxxxxxx"

脚本主程序

 main.py,定时执行,每周一-周五执行,每隔10分钟执行

#获取日志易信息,统计查询大于500的错误,发送钉钉#直接使用curl命令调用接口,大于500,发送钉钉import subprocessimport requestsimport configimport jsonimport loggingimport osimport sysimport refrom datetime import datetimeimport timeimport pandas as pdimport openpyxlfrom openpyxl.styles import Font,Colorimport smtplib, sslfrom email.mime.text import MIMEText #邮件正文from email.mime.multipart import MIMEMultipart #邮件主体from email.header import Header #邮件头,标题、收件人等from email.mime.text import MIMETextfrom email.mime.multipart import MIMEMultipartfrom email.mime.base import MIMEBasefrom email import encoders

#查询日志易API#增加表格样式标记#version 2.0#yangchao
"""获取信息1、获取总量2、错误数(大于500)3、平均时长4、独立访客5、最大相应时长6、最小相应时长7、错误数(4??)
"""
#配置日志abspath=os.path.abspath(sys.argv[0])current_path=os.path.dirname(abspath)logging.basicConfig(filename=current_path+'/info.log',encoding='utf-8',level=logging.INFO,format='%(asctime)s - %(levelname)s - %(message)s')
#格式化时间time_now=datetime.now()format_date=time_now.strftime("%Y-%m-%d")

class RiZhiYi_Api(): """ 通过日志易获取的信息 """ def __init__(self) -> None: """ 初始化信息 """ self.Err5xx_dict={} #保存错误字典 self.Err5xx_list=[] self.dingding_token=config.dingding_token
self.uri=config.rizhiyi_uri self.token=config.rizhiyi_token self.username=config.rizhiyi_username self.passwd=config.rizhiyi_passwd self.url=str(self.uri+self.token+"/"+self.username+"/spl/search") #请求头 self.header={'Content-Type': 'application/json'} #Http的base认证 self.auth=requests.auth.HTTPBasicAuth(self.username,self.passwd)
#基本查询参数 #任务名 self.task_name={"task_name":"my_search"} #时间范围,5分钟前 self.time_range={"time_range":"-10m,now"} #从首页显示 self.page={"page":0} #分类 self.category={"category":"search"} #查询语句 self.query={"query":""} #构建查询参数字典 self.params={} self.params.update(self.task_name) self.params.update(self.page) self.params.update(self.category) self.params.update(self.time_range) #base认证 self.credentials=requests.auth.HTTPBasicAuth(self.username,self.passwd)
def GetErr5xx(self,sysname:str)->int: """ 获取错误数大于500 计算错误占比 sysname:系统名 """ sql="logtype:Nginx AND Nginx.url:\/" +sysname+"* AND NOT `JS_CSS_JPG_FONT` AND Nginx.status:5?? | stats count() as cnt" self.query["query"]=sql self.params.update(self.query) try: respson=requests.get(self.url,params=self.params,auth=self.credentials,headers=self.header) content=respson.text if respson.status_code ==200 and "error_code" not in content: #logging.info("查询5xx成功") pass elif respson.status_code !=200 and "error_code" in content: logging.error("查询5XX失败%s:",content) else: logging.error("查询5XX失败%s:",content) except Exception as e: logging.error("查询5XX失败%s:",e) raise Exception("查询5XX失败%s:",e) content_dict=json.loads(content) #pprint.pprint(content_dict) try: cnt=content_dict["result"]["sheets"]["rows"][0]["cnt"] print(cnt) except Exception as e: logging.error("查询5XX失败:%s",e) raise Exception("查询5XX失败:%s",e) info=sysname+":"+str(cnt) #logging.info("查询5XX成功:%s",info) return int(cnt)
def GetErr5xxUrl(self,sysname:str)->str: """ 获取错误数大于500的接口地址 计算错误占比 sysname:系统名 """ err_info=[] sql="logtype:Nginx AND Nginx.url:\/" +sysname+"* AND NOT `JS_CSS_JPG_FONT` AND Nginx.status:5??" self.query["query"]=sql self.params.update(self.query) respson=requests.get(self.url,params=self.params,auth=self.credentials,headers=self.header) content=respson.text content_dict=json.loads(content) #pprint.pprint(content_dict) try: count=content_dict["result"]["sheets"]["rows"][:] if len(count) !=0: for i in count: message=i["raw_message"] logging.info("查询5XX信息:%s",message) if re.search("\[\d{2}\/[a-zA-Z]{3}\/\d{4}:\d{2}:\d{2}:\d{2} \+\d{4}\] (\w+) ([^ ]+) \S+ (\d{3})",message): if re.search('"https([^"]*)"',message): message_1=(re.search("\[\d{2}\/[a-zA-Z]{3}\/\d{4}:\d{2}:\d{2}:\d{2} \+\d{4}\] (\w+) ([^ ]+) \S+ (\d{3})",message)).group(0) message_2=(re.search('"https([^"]*)"',message)).group(0) message_new=message_1+" "+message_2 err_info.append(message_new) except Exception as e: logging.error("查询5XX信息失败:%s",e) raise Exception("查询5XX信息失败:%s",e) str_message='\n'.join(err_info) return str_message def SendDingDing(self)->None: #循环字典,读取指标 for name,sysname in config.SysNameList.items(): Err5xx=self.GetErr5xx(sysname) Err5xxUrl=self.GetErr5xxUrl(sysname)
if Err5xx >=3: if Err5xxUrl != "": logging.info("查询到5xx 大于3次:%s: %s",sysname,Err5xx) #self.Err5xx_dict[name]="5XX错误次数:"+str(Err5xx)+",系统名:"+sysname +'详细的接口信息:\r'+Err5xxUrl #self.Err5xx_dict[sysname]='详细的接口信息:\r'+Err5xxUrl i=name+",系统名:"+sysname +" 5XX错误次数:"+str(Err5xx)+'\n详细的接口信息:\n'+Err5xxUrl self.Err5xx_list.append(i) #error_str=json.dumps(self.Err5xx_dict) #error_str=str(self.Err5xx_dict) error_str="\n".join(self.Err5xx_list) data={ "msgtype":"text", "text":{ "content":"根据日志易查询,当前系统过去10分钟,发现5xx错误大于3次!!!\n"+error_str+"\n请立即检查!!!\n" } } try: requests.post(self.dingding_token,json=data) except Exception as e: logging.error("通过日志易,发送钉钉消息失败:%s",e)


class Curl_Api(): #通过curl直接访问接口,返回 def __init__(self) -> None: #读取配置文件 self.api_interface_dict=config.api_interface_dict self.dingding_token=config.dingding_token self.error_list=[] def TestApi(self)->dict: for k,v in self.api_interface_dict.items(): try: #command=subprocess.check_output(['curl','-X','GET','-H','"Content-Type: application/json"',v,'--insecure']) #command=subprocess.check_output(['curl',v,'--insecure']) command=subprocess.check_output(['curl','-I','-s','-w','"%{http_code}',v,'--insecure']) command_text=command.decode('utf-8') #command_text=command_text.replace("null","None") if command_text =="" or command_text==None: logging.error("接口没有返回值!:%s",v) else: if re.match("HTTP/[\d.]+\s+(\d+)",command_text): code=(re.match("HTTP/[\d.]+\s+(\d+)",command_text)).group(0) code_int=int(code.split(" ")[1].strip()) #if re.match("\d{3}",code): # code_int=int((re.match("\d{3}",code)).group(0)) if code_int >=500: self.error_list.append("接口url:"+v+",响应码:"+str(code_int)) logging.error("检测响应码大于500的接口:%s,响应码:%s",v,code) else: self.error_list.append("接口url:"+v+",响应码:"+str(code_int)) logging.info("接口:%s,响应码:%s",v,code) except subprocess.CalledProcessError as e: logging.error("读取接口失败:%s",e) return self.error_list def SendDingDing(self): if len(self.error_list) !=0 : #or self.error_list !=[]: #error_str=json.dumps(self.error_dict) error_str="\n".join(self.error_list) data={ "msgtype":"text", "text":{ "content":"通过curl命令检查,当前发生接口错误!!!\n"+error_str+"\n请立即检查!!!" } }# else:# data={# "msgtype":"text",# "text":{# "content":"测试一下!"# }# } try: requests.post(self.dingding_token,json=data) except Exception as e: logging.error("发送钉钉消息失败%s",e)

if __name__=="__main__": start_time=time.time() logging.info("程序开始运行%s",format_date) RiZhiyiApi=RiZhiYi_Api() CurlApi=Curl_Api() RiZhiyiApi.SendDingDing() CurlApi.TestApi() CurlApi.SendDingDing() end_time=time.time() logging.info("程序结束运行,共耗时%s秒",str(end_time-start_time))

使用效果


链接:https://blog.51cto.com/u_11555417/8386802

(版权归原作者所有,侵删)


微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
深度好文|Python变身表白利器!又被恋爱脑的理工男秀到了!跨语言编程:在C#应用程序中调用Python在线编写和运行Python代码 使用介绍Textual:为 Python 增加漂亮的文本用户界面(TUI) | Linux 中国一个 Python 的轻量级搜索工具Python中常用内置函数熬夜爆肝,用Python赚了4w!文学城里写欧洲文化,艺术的帖子,鸿发超市「2000 万美元」买下82街前Walmart超市!开设第4家Hông Phát分店!写python爬虫,你永远绕不过去代理问题GPT-4绝对性能暴涨74.4%!UIUC苹果华人团队提出CodeAct,用Python代码统一LLM智能体行动由首次上海飞往蒙特利尔的机票票根引起的追忆(下)刷个B站的功夫就能把Python学了,哈佛这门线上课程,太强了!深入了解Apache 日志,Apache 日志分析工具超级好用的Python模块——glob模块大神是如何用 python 对远程服务器进行命令或文件操作的?更方便了!Python在线编程支持中文了!给你一张知识图谱,Python 就这么学 | 极客时间太方便了!无须安装软件,「在线」编写Python代码!Python实现Excel拆分与合并2024 年 3 月编程语言排行榜,Python 与其他语言之间的差距从未如此之大!自学12h!人在英国,我线上白嫖到了密歇根全套Python课程!imageio,一个Python处理图片的工具包【赠送GPT账号】如何使用ChatGPT完成科研、程序开发、论文写作等,看看这篇!再见Excel,你好Python10个python爬虫入门实例Python装饰器应用示例gorm中如何使用枚举值2024技术招聘状况:AI工具被视为作弊,Python需求最大亵渎义字纪念他什么? ——也写于谢晋诞辰百周年清华官方出品Python漫画教学书!零基础留学生3天速成刷个B站的功夫就把Python入门了,哈佛的线上课程,太强了!加快10倍!GPT-Fast来了!使用原生 PyTorch 加速生成式 AIAll for one and one for all.The Three Musketeers
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。