自动化报警的实现

监控最终效果

最近从主产品被调到了火星,技术不到家的我感到压力很大啊。提代码的时候也更加小心翼翼了,生怕搞坏了线上环境。

于是,我就打算给自己做个监控,好让我能快速的发现问题,然后改掉bug。考虑到公司某些规定,就不详细介绍了。下面简答的描述下,有个思路就行。

思路就是:

1. 分析Nginx错误日志,用正则匹配出对应内容
2. SVN blame出错误代码的作者。
3. 借助钉钉的群聊机器人,及时发送出去。

在实现的过程中,遇到了很多问题。大概有这么几个:
1、 服务器端口限制的比较死,不能单独给自己开对外访问的端口。
2、 重复错误的触发。
3、 SVN blame认证问题(没解决。。。)
... ...


整体准备采用Client和Server的模式。

Client内容如下:

使用一个Python脚本,借助crontab监控错误日志,并正则匹配对应的内容,发送给Server端处理。

#!/usr/bin python
# coding: utf8
import sys
reload(sys)
sys.setdefaultencoding("utf8")
import re
import json
import urllib2
import time

# 2017/11/17 16:05:01 [error] 4004#0: *246620391 FastCGI sent in stderr: "PHP message: PHP Fatal error:  Function name must be a string in /home/wwwroot/api.newtv.com/live.class.php on line 2242" while reading response header from upstream, client: 192.168.30.100, server: api.changbalive.com, request: "GET /api.php?ac=recordsingsong&curuserid=2635267&channelsrc=appstore&version=1.9.5&token=T777936552f7571e&bless=0&macaddress=A4D0E95D-AB54-48A1-BCC8-3EB0A530B2A7&ismember=0&openudid=d7be3882344bb889cd6c451880df1a834f1af960&systemversion=10.3.3&device=iPhone7,1&broken=0&songid=867712&secret=483f47528d HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "api.changbalive.com"

# 返回值含义:tuple元组内依次为:报错时间,错误类别,错误描述,出错文件全路径,出错位置行数
def parse_errorlog(line):
    reg = re.compile(r"(.*?) \[error\] .*?PHP message:(.*?): (.*?) in (.*?) on line (\d+).*");
    result = re.findall(reg, line)
    # print len(result)
    # print result
    return result
def get_errorlog(type='api_newtv_error.log'):
    path = "/var/log/nginx/{}".format(type)
    result = []
    with open(path, 'r') as file:
        lines = file.readlines()
        file.close()
    for line in lines:
        if line is not None:
            result.append(line)
    return result
//TODO
// 使用HTTP请求,将找到的错误信息emit到Server端

Server内容

用到的文件大致有这么几个。

findbugauthor.sh  receive.php   utils.php

findbugauthor.sh

#!/usr/bin bash
export $PATH
DIRPATH=$1
FILENAME=$2
LINE=$3

cd $DIRPATH
AUTHOR=`svn blame $FILENAME | head -$LINE | tail -1 | awk '{print $2}'`
echo $AUTHOR

utils.php

<?php
header("Content-Type:text/html;charset=UTF-8");

function getuniquecode($path, $line){
    $errorcode = md5("{$path}{$line}");
    return $errorcode;
}
function phpPost( $url, $post = '', $timeout = 5  ){
    if( empty( $url  )  ){
          return ;

    }
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);

    if( $post != '' && !empty( $post  )  ){
         curl_setopt($ch, CURLOPT_POST, 1);
         curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
         curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'Content-Length: ' . strlen($post)));
     }
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    $result = curl_exec($ch);
    curl_close($ch);
    return $result;
}
function getbashoutput($path, $line) {
    $dirpath = substr($path, 0, strpos($path, basename($path)));
    $filename = basename($path);
    $result = "";
    exec("bash ./findbugauthor.sh {$dirpath} {$filename} {$line} 2>&1", $result);
    return $result[1];
}
//getbashoutput("/home/wwwroot/巴拉巴拉/你的文件ListService.php", 142);
class RedisHelper{
    private static $_instance = null;
    const APINEWTVERROR_KEY = "zpinewtvcom:error:zset";
    const NOTIFY_KEY = "apinewtvcom:notify:hash";
    private function __construct(){
        $this->redis = new Redis();
        $this->redis->connect("127.0.0.1", 6379, 7);
    }
    public static function getInstance(){
        if( self::$_instance == null ) {
            self::$_instance = new RedisHelper();
        }
        return self::$_instance;
    }

    public function incrErrorNumber($errorcode, $number=1){
        $this->redis->zIncrBy(self::APINEWTVERROR_KEY, 1, $errorcode);
    }

    public function getErrorNumber($errorcode) {
        return intval($this->redis->zScore(self::APINEWTVERROR_KEY, $errorcode));
    }

    public function updateNotifyDate($errorcode){
        $this->redis->hSet(self::NOTIFY_KEY, $errorcode, date("Ymd"));
    }

    public function getNotifyDate($errorcode) {
        return $this->redis->hGet(self::NOTIFY_KEY, $errorcode);
    }
    public function getFrequentErrors($number=7) {
        return $this->redis->zRevRange(self::APINEWTVERROR_KEY, 0, $number, true);
    }
}
$template = <<<EOF
{
    "msgtype": "text",
    "text": {
        "content": "我就是我, 是不一样的烟火"

},
    "at": {
        "atMobiles": [
            "156xxxx8827",
            "189xxxx8325",

],
        "isAtAll": false

}

}
EOF;
class Notifier{
    private static $instance = null;
    private function __construct() {
        $this->url = "https://oapi.dingtalk.com/robot/send?access_token=b716e1f39b7fc7afbea04b2巴拉巴拉d4bb79db65a117d589f886d1757";
    }
    public static function getInstance(){
        if(self::$instance==null) {
            self::$instance = new Notifier();
        }
        return self::$instance;
    }
    public function notify($msg){
        global $template;
        $data = json_decode($template, true);
        $data['text']['content'] = $msg;
        $data['at']['atMobiles'] = array(15801479216, );
        $data['at']['isAtAll'] = false;
        $data['msgtype'] = "text";
        $result = phpPost($this->url, json_encode($data));
        return $result;
    }
}

receive.php

<?php
header("Content-Type:text/html;charset=UTF-8");
require __DIR__."/utils.php";

$time = isset($_REQUEST['time'])?strval($_REQUEST['time']):"";
$level = isset($_REQUEST['level'])?strval($_REQUEST['level']):"";
$description = isset($_REQUEST['description'])?strval($_REQUEST['description']):"";
$fullpath = isset($_REQUEST['fullpath'])?strval($_REQUEST['fullpath']):"";
$linenumber = isset($_REQUEST['linenumber'])?intval($_REQUEST['linenumber']):0;

if(empty($time) || empty($level) || empty($description) || empty($fullpath) || empty($linenumber)) {
    echo json_encode(array("errcode"=>-1, "errmsg"=>"请求参数不完整"));
}

$errorcode = getuniquecode($fullpath, $linenumber);
$helper = RedisHelper::getInstance();
$helper->incrErrorNumber($errorcode);
$bugauthor = getbashoutput($fullpath, $linenumber);
$notify = Notifier::getInstance();
var_dump($errorcode);
echo "\n";
$errors = $helper->getFrequentErrors(7);
var_dump($errors);
foreach($errors as $uniquecode=>$numbers) {
    if(intval($errorcode) == intval($uniquecode)) {
        $msg = "Bug 时间:{$time}\nBug级别:{$level}\n错误描述:{$description}\n文件全路径:{$fullpath}\n出错行数:{$linenumber}\n代码负责人:{$bugauthor}\n";
        $notify->notify($msg);
    }
}

实现的效果

监控最终效果

不足之处

报警的触发机制还没完善,其实这块要做的内容会很多的,根据不同的场景选择不同的策略很重要,但是要做到灵活的处理,每一个很好的设计是不行的,这里有兴趣的可以自己思考思考。

crontab的时间间隔也是个问题,太小了对服务器压力稍微有一点点的影响(虽然这基本上也没什么影响,但是时间片太小了,触发机制就得跟着更改下);时间片太大了,报警的灵敏度就下降了,也就失去了报警的意义。

总的来说,思路很简单,但是真的去实现起来并能很好的应用到开发中,还是有很长的路要走的。这里就当是抛砖引玉吧。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容