撰文:R編

前一陣子在粉絲團分享幾張我們Web server在Mattermost說的話,就有人想問問我們用哪個套件做的。不巧我很喜歡用原始簡單的方式做,所以他就邀請我分享我的做法。

我的做法很單純,就是用crontab去找一找機器上的php error log,然後發到Mattermost上。(註:Mattermost可以把它看作社群版的Slack)


Step 1. 設定php error log

php.ini

# 顯示Error、Warning、Notice
error_reporting=E_ALL & ~E_DEPRECATED & ~E_STRICT
# 不顯示在畫面上,只寫在log裡
display_errors = Off
log_errors = On

如果你是使用apache的話,只需要在設定virtual host時,指定error寫到哪,如果使用php-fpm,就要另外指定php-fpm的error log位置

Apache

<VirtualHost *:80>
# 前略
ErrorLog logs/php-error.log
</VirtualHost>

php-fpm.conf

error_log = /var/opt/remi/phpXX/log/php-fpm/php-error.log


Step 2. 用bash撈取error log

grep_log.sh

#!/bin/bash

# 列出要擷取的關鍵字
KEYWORD='PDO|error|Error|Warning|Notice'

# 因為Apache和php-fpm的log位置和時間格式都不一樣
# 我們機器有的使用Apache,有的使用nginx + php-fpm,所以在這裡做區分

if [[ $HOSTNAME =~ "APACHE_HOST" ]]; then
    NOW=$(date -d '-1 minute' +"%b %d %H:%M");
    LOGFILE="/var/log/httpd/api-error_log"
else
    NOW=$(date -d '-1 minute' +"%d-%b-%Y %H:%M");
    LOGFILE="/var/opt/remi/php70/log/php-fpm/error.log"
fi

# 擷取前一分鐘的Log,暫存在tmp.log,並Call php程式去發送
cat "$LOGFILE"|egrep -i "$KEYWORD" |grep "$NOW" > tmp.log
php ./push_matter.php tmp.log

Step 3. 用PHP發送Log

在這邊使用php發送的理由是我對php比較熟悉,使用php來處理字串對我來說比較容易。

push_matter.php

<?php
define('MATTERMOST_URL', 'https://XXXXX/hooks/OOOOOO');
define('MATTERMOST_CHANNEL', 'XXXXXX');

# 取得參數
$filename = $argv[1];
$hostname = gethostname();

# 從暫存log中取出資料
$msg = trim(file_get_contents($filename));

# 濾掉不要的log的pattern
# 例如log rotated, php-fpm child生命終止, php-fpm啟動訊息,或其他要過的log格式

$skip_log_patterns = [
    '/NOTICE:\ \[pool\ www\]\ child\ \d+\ exited\ with\ code\ 0/',
    '/NOTICE:\ \[pool\ www\]\ child\ \d+\ started/',
    '/NOTICE:\ error\ log\ file\ re\-opened/',
    '/NOTICE:\ ready\ to\ handle\ connections/',
    '/NOTICE:\ systemd\ monitor\ interval\ set\ to/',
];

# Fatal Error的格式,只要是Fatal Error,就會tag全channel
$fatal_error_patterns = [
    '/ERROR/',
    '/Fatal\ error/',
];

# 機器重開是,php-fpm啟動訊息,我們用這個訊息判斷auto scaling開機器起來了
$fpm_pattern = [
    '/NOTICE:\ fpm\ is\ running,\ pid/',
];

# 把訊息拆成Array
$msg_lines = explode("\n", $msg);
$lines = [];
$boot  = false;

# 一行一行處理
foreach ($msg_lines as $line) {
    $skip = false;

    # 如果符合略過的log, 就標記略過
    foreach ($skip_log_patterns as $pattern) {
        if (preg_match($pattern, $line)) {
            $skip = true;
            break;
        }
    }

    # 如果符合php-fpm啟動訊息,我們就隨機選一句機器說的話,並標記這是啟動訊息
    foreach ($fpm_pattern as $pattern) {
        if (preg_match($pattern, $line)) {
            $skip = true;
            $launch_msgs = [
                '我上工了 \OvO/',
                '需要我了是不是 :hehe: ',
                '尖峰時刻來臨 :amaze: ',
                '槓上開花加一台(主機)',
                '.....原來我只是備胎....(默)',
                '愛德華……大哥哥……',
                '所羅門喲,我又回來了!',
                '應付完這批流量,我就要回老家結婚了。',
                '陌生的天花板',
                # ....以下略
            ];
            $lines[] = $launch_msgs[rand(0, count($launch_msgs) - 1)];
            $boot = true;
            break;
        }
    }

    # 如果被標記成略過的訊息,則繼續處理下一句
    if ($skip) {
        continue;
    }

    # 把要通知的訊息裝進Array
    $lines[] = $line;
}

# 最後組裝要發送的訊息
$msg = implode("\n", $lines);

# 如果訊息都略過了,則離開
if ('' == $msg) {
    exit;
}

$type = 'UNKNOWN';
$text = '';
$fatal = false;
# 判斷是不是Fatal Error
foreach ($fatal_error_patterns as $pattern) {
    if (preg_match($pattern, $msg)) {
        $fatal = true;
        break;
    }
}

# 如果是Fatal Error,訊息要tag channel,不同層級的log,會有輕重不一的呈現方式
if ($fatal) {
    $type = 'ERROR :rotating_light: ';
    $text.= '@channel ';
} else if (preg_match('/WARNING/', $msg)) {
    $type = 'WARNING :warning: ';
} else if (preg_match('/NOTICE/', $msg)) {
    $type = 'NOTICE';
}

# 如果是機器啟動訊息,文案則有不同的格式
if ($boot) {
    $text.= "[$hostname] 伺服器啟動,機器表示: \n";
} else {
    $text.= "[$hostname] PHP警報: $type\n";
}

# 使用markdown的語法,用log block包裝訊息
$text.= "~~~Verilog\n" .
$msg .
"\n~~~";

# 發送
send($text);

# 發送程式,這就不解釋了
function send($text)
{
    $payload = [
        'username' => 'pp-bot',
        'channel' => MATTERMOST_CHANNEL,
        'text' => $text,
    ];
    $headers = [
        "Content-type: application/json;charset='utf-8'",
    ];
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, MATTERMOST_URL);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_exec($ch);
    curl_close($ch);
}


Step 4. 設定crontab

沒意外的話,web server的log預設是root權限,所以crontab可以設在/etc/crontab去跑。如果覺得不安全的話,也可以改一下log的權限,讓你指定的用戶可以讀。

/etc/crontab

  *  *  *  *  *  root       cd /path/to/your/scripts && bash ./grep_log.sh


這樣你就可以在你的Channel上看到php error了,而且是每分鐘更新。

figure-1

拍手 拍手
6 次拍手
拍手 拍手
追蹤

推薦文章

您需要 後才能開始留言
還沒有人討論誒,快來搶沙發...
聲音節目
沒有描述
--:--
--:--
1.0x
播放速度
2.0x
1.75x
1.5x
1.25x
1.0x
0.75x
收藏節目
播放清單
沒有播放清單
沒有待播放的清單
返回播放器
接著播放
清除全部
沒有待播放的清單