日志监控是企业监控系统里面很重要的一个环节,通过日志监控可以提前发现网络设备或者系统存在的未知或者潜在的风险,让运维人员及时干预,减少损失。常见的监控系统Zabbix、Prometheus都支持日志的监控。但由于在数据量、搜索、可视化、报警等方面都比较弱化,推荐使用专业的日志收集与告警工具ELK或者Graylog。下面通过Graylog实现系统日志的采集、分析、报表的展示和告警,为广大网友提供一个思路,其它的如业务日志、MySQL错误日志、网络设备的日志、微服务的日志都是一个道理,要学会举一反三。

一、Rsyslog安装配置

1.1 Rsyslog 服务端的配置,IP为 192.168.10.100。

yum install -y rsyslog
vim /etc/rsyslog.conf
# 通过UDP协调 514 端口接收日志
$ModLoad imudp.so
$UDPServerRun 514
# 通过TCP协议 1515 端口接收日志
$ModLoad imtcp.so  
$InputTCPServerRun 1515
# 重启服务
systemctl restart rsyslog

1.2 Rsyslog客户端的配置。

通过TCP协议把日志传输给Rsyslog Server服务器上面。

vim /etc/rsyslog.conf
#使用TCP协议,一个@表示UDP协议,两个@表示TCP协议,默认端口不需要加端口,自定义的需要加端口。
# *.*       @192.168.10.100
#使用TCP协议
*.*       @@192.168.10.100:1515
systemctl restart rsyslog

1.3 Rsyslog日志级别。

Rsyslog 日志级别总共有7种,数字代号为0-7,数字越小,代表级别越高。默认收集的rsyslog没有记录日志的级别,在Rsyslog Server上面通过模板的方式让把日志的级别记录到日志里面,方便后期告警。

none 不算是一个等级,它表示不记录服务的所有信息 0 emerg 系统不可用 1 alert 特别留意的报警信息 2 crit 非常严重的状况 3 err 错误信息 4 warning 警告信息 5 notice 稍微要注意的信息 6 info 正常信息 7 debug 调试信息,开发人员使用

$template GRAYLOGSYSLOG,"%timegenerated% %HOSTNAME% %fromhost-ip% %syslogtag%%msg% %syslogseverity%\n"
$ActionFileDefaultTemplate GRAYLOGSYSLOG
#本地保存日志
$template Remote,"/data/logs/linux/%FROMHOST-IP%/%FROMHOST-IP%.log" *
*.* @@192.168.10.100:1515;GRAYLOGSYSLOG

二、通过Graylog收集系统日志

通过Graylog的System/Inputs新建一个类型为Syslog TCP的 INPUT,端口为1515。然后通过INPUT的 Show received messages就可以看到收集过来的日志,日志最后的数字代表系统日志的级别,即我们模板里面配置的syslogseverity。 graylog_01.jpg

三、Graylog 邮件告警

Graylog 告警的流程大概是日志通过INPU进入系统,然后通过Streams过滤需要告警的关键字(可以通过message过滤关键字、通过日志级别告警),最后通过Alert定义告警的条件和需要接收的告警人。

3.1 开启Graylog邮件报警功能。

修改graylog配置文件,开启Graylog发邮件的功能,通过系统自带的postfix就可以发送邮件告警。

# Email transport
transport_email_enabled = true
transport_email_hostname = 192.168.10.100
transport_email_port = 25
transport_email_use_auth = false
transport_email_subject_prefix = [graylog]
transport_email_from_email = graylog@110.zmzblog.com
#重启服务
[root@graylog-server ~]# /etc/init.d/graylogctl start

3.2 新建Stream。

Stream用于过滤需要告警的日志,可以通过Fields过滤或者通过关键字过滤,其它选型选择默认即可。

图片.png

3.3 定义Rsyslog日志格式。

虽然我们在收集日志的时候定义了日志的级别,但graylog需要通过Fields提取日志的级别。通过INPUT,点击Manae extractors 新建提取器。通过Load Message 选择一条日志,在通过Regular expression正则的方式匹配Fields。Add Converter是为了把日志级别转换成数字,方便统计分析。 图片.png

3.4 通过Streams添加告警规则。

在Streams点击Manage Rules,新建告警日志的规则。即系统日志基本为0或者1,或者出现OOM等关键字的时候告警。 图片.png

3.5 设置告警Alerts。

首先新建通过Alerts->Notifications新建Notification。 图片.png

其次通过Alerts->Event Definition 新建Event,并关联Streams,在Noitification关联上面新建的Notification即可。

3.6 邮件告警测试。

当系统出现日志级别高的错误是会触发告警,下面是邮件告警的内容。

192.168.20.244  Mar 20 18:29:42 ZBXDB51 kernel: journal commit I/O error 0
192.168.20.244  Mar 20 18:29:42 ZBXDB51 kernel: journal commit I/O error 0
192.168.20.244  Mar 20 18:29:42 ZBXDB51 kernel: journal commit I/O error 0
192.168.20.244  Mar 20 18:29:42 ZBXDB51 kernel: journal commit I/O error 0

四、Graylog微信告警

因为系统的日志的级别为0-1,相对于系统来讲,已经出现很严重的问题,需要运维人员立即确认处理。因此需要通过微信或者短信的方式通知相关人员。Graylog有丰富完善的API接口,Graylog Api接口:System/Nodes ->Nodes->API browser。通过下面接口用Python构造程序,通过定时任务从而实现微信告警。 图片.png

[root@graylog-server ~]# cat /zabbix/python/graylog/getgrayloglinuxsystemlogsalert.py     
#!/usr/bin/env python
#_*_coding:utf-8 _*_
#朴实的追梦者

import os
import sys
import json
import time
import MySQLdb
import requests
import datetime
import simplejson


reload(sys)
sys.setdefaultencoding('utf-8')


startTime = ( datetime.datetime.now() - datetime.timedelta(seconds=600)).strftime('%Y-%m-%d %H:%M:%S')
endTime = (datetime.datetime.now()).strftime('%Y-%m-%d %H:%M:%S')


def gettoken(corpid, corpsecret):
    gettoken_url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=' + corpid + '&corpsecret=' + corpsecret
    try:
        token_file = requests.get(gettoken_url)
        token_data = token_file.text
        token_json = json.loads(token_data)
        token_json.keys()
        token = token_json['access_token']
        #print (token)
        return token
    except requests.RequestException as e:
        print(e)


def senddata(access_token, user, msg_type, **msg_dic):
    send_url = 'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=' + access_token
    send_values = {
        #"touser":user,      
        "toparty": user,       
        "msgtype": msg_type,      
        "agentid": "xxxxxx",
        "safe": "0"
    }


    for k,v in msg_dic.items():
        send_values[msg_type] = {k:v}
    print send_values
    send_data = simplejson.dumps(send_values, ensure_ascii=False).encode('utf-8')
    send_request = requests.post(send_url, send_data)
    print(send_request)
    response = json.loads(send_request.text)
    print str(response)


def getmediaid(token):
    url = 'https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=' + token + '&type=file'
    files = {'file': open(filename)}
    r = requests.post(url, files=files)
    re = json.loads(r.text)
    return re['media_id']


def getGraylogMessage():
    
    url = 'http://192.168.10.100:8080/api/search/universal/absolute'


    headers = {
        'Content-Type': "application/json",
        'user-agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36"


    }


    data = {
        # 通过Graylog 查询语法
        "query": "rsyslogs_level:<=1",
        #"from": "2021-06-24 00:00:00",
        "from": startTime,
        #"to": "2021-06-25 10:00:00",
        "to": endTime,
        "limit": "10",
        "decorate": "true",
    }
   
    res = requests.get(url, params=data, headers=headers, auth=('admin', 'xxxxxx'))
    print res.status_code
    
    try:
        list = res.json()['messages']
    except KeyError as e:
        print "No json Object could be decoded."
    return list


if __name__ == '__main__':
    
    list = getGraylogMessage()
    filename = "/zabbix/python/graylog/logs/graylogLinuxOsErr.txt"
    corpid = 'xxxxxx'
    corpsecret = 'xxxxxx'
    accesstoken = gettoken(corpid, corpsecret)


    if os.path.exists(filename):
        os.remove(filename)


    print len(list)
    # 日志数量小于等于2的时候通过消息告警
    if len(list) != 0 and len(list) <=2:
        for line in list:
            content = "[graylog] %s %s Logs Level:%d\n" % (line['message']['ip'],line['message']['message'],line['message']['rsyslogs_level'])
            print content
            msg_dic = {"content" : content}
            senddata(accesstoken,2,'text',**msg_dic)

    # 日志数量大于2条的时候通过推送文件方式告警
    elif len(list) != 0 and len(list) >2:
        for line in list:
            content = "[graylog] %s %s Logs Level:%d\n" % (line['message']['ip'],line['message']['message'],line['message']['rsyslogs_level'])
            with open(filename, 'a') as f:
                f.write(content + '\n')
        msg_dic = {"media_id" : getmediaid(accesstoken)}
        senddata(accesstoken,8,'file',**msg_dic)

五、日志可视化

日志可视化是日志收集与分析不可缺少的一部分,通过日志可视化可以一眼确认线上服务器是否有问题。要做好可视化,首先需要根据3.3的方法通过正则的方式获取每条日志的主机名、IP地址、错误类型、日志级别等,然后可以通过不通的维度进行分析。 5.1 通过Dashboards新建一个Dashboards,名字为xxx 服务器系统日志报表。 5.2 从System->INPUT->Show received messages进入,从左侧的Fields把rsyslogs_level移入到右边的界面里面,在通过添加Chart的方法定制各种报表。 图片.png

5.3 最终的报表展示如下,可以通过主机、日志类型、错误级别等不同的维度进行分析。 图片.png

六、总结

经过多年的运维经验发现,很多系统的错误与故障都与日志相关,比如说网络设备的光模块故障、电源模块等,系统层面的内核bug、文件系统只读、内存降级等故障都可以通过日志告警及时发现。通过日志监控不仅能及时发现问题,而且可以省去人工巡检的时间,把时间花在优化系统和学习新新技术上面。

七、优化

通过API定时告警一方面有延时,另一方面由于query内容不同(系统日志、网络设备、MySQL、中间件等),需要维护多个程序,维护管理麻烦。后来通过Graylog HTTP Notification告警方式,开发了webhooks程序,通过程序在后台实时监听如果有告警graylog会把告警推送到webhooks程序上面,通过程序处理不同任务的告警内容,从而实现日志实时告警。 7.1 通过Alerts->Notification->Create Notification 新建一个机器人告警。 gray_webhooks.jpg 7.2 通过Python程序处理告警消息,然后把告警推送给微信机器人。这里的taskname为graylog Streams名称。

# !/usr/bin/env python
# _*_ coding: utf-8 _*_
#朴实的追梦者

from flask import Flask, request
import re
import sys
import time
import requests
import json
import datetime
import simplejson

logslevel = {0:"Emerg",1:"Alerts",2:"Crit",3:"Error",4:"Warning",5:"Notice",6:"Info",7:"Debug"}


def wechat(content):

    headers = {'Content-Type': 'application/json;charset=utf-8'}
    api_url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxx'
    json_text=  {
        "msgtype": "markdown",
        "markdown": {
             "content": "<font color=\"info\">**[Graylog日志实时告警]**</font> \n %s" % content 
          }
     }

    requests.post(api_url,json.dumps(json_text),headers=headers).content

app = Flask(__name__)
@app.route("/send_wechat", methods=["POST"])
def send_webcaht():
    content = ''
    info = request.json
    print("#########:",info, type(info))

    taskname = info['event_definition_title']
    
    if taskname == "Linux Server Rsyslog Warning":
        for i in info['backlog']:
            level = i['fields']['rsyslogs_level']
            line = """%s %s <font color=\"comment\">Loglevel</font>:<font color=\"warning\">%s:%s</font>\n"""  % (i['fields']['ip'],i['message'],level,logslevel[level])
            content = content + line

    elif taskname == "Network Device Logs Warning" or taskname == "ASA Network Device Logs Warning":
        for i in info['backlog']:
            level = i["fields"]["level"]
            line = """%s %s <font color=\"comment\">Loglevel</font>:<font color=\"warning\">%s:%s</font>\n""" % ( i["source"],i["message"],level,logslevel[level])
            content = content + line

    elif taskname == "MySQL Error Logs  Warning":
        for i in info['backlog']:
            line = """%s %s %s <font color=\"comment\">Loglevel</font>:<font color=\"warning\">%s</font>\n"""  % (i["fields"]["name"],i["fields"]["gl2_remote_ip"],i["message"],i['fields']['logs_level'])
            content = content + line

    elif taskname == "Tuxedo Error Logs Warning":
        for i in info['backlog']:
            line = """%s %s\n"""  %  (i['fields']['gl2_remote_ip'],i['message'])
            content = content + line


    elif taskname == "Event Definition Test Title":
        line = line = """<font color=\"warning\">%s</font>\n"""  % info['event']['message']
        content = content + line
    else:
        content = info

    if len(content) != 0:
        wechat(content)
        
           
    return "成功"

if __name__ == "__main__":
    app.run("192.168.100.247", 6000)

7.3 如果有重要的日志微信会实时收到告警消息。 graylog_bot.jpg