Skip to content

Latest commit

 

History

History
176 lines (150 loc) · 5.36 KB

如何使用PHP脚本来增量订阅错误日志?.md

File metadata and controls

176 lines (150 loc) · 5.36 KB

如何使用 PHP 脚本来增量订阅错误日志?

业务场景

目前公司中自己负责的后端系统采用的是 PHP 语言实现,写代码避免不了出现一些 BUG,此时 PHP 就会将错误日志写到指定的错误日志文件中,如果能够及时的看到这些错误日志,就能及时发现和解决问题,故写了一个脚本来增量订阅 PHP 的错误日志

#!/bin/env php
<?php

PHP_SAPI !== 'cli' && exit('ONLY CLI');
set_time_limit(0);

/********************************************* 常量定义 begin ***********************************************************/
// 错误日志文件位置
const ERROR_LOG_PATH        = '/data/logs/php/error.log';
// 当前进程名称
const CURRENT_PROCESS_NAME  = 'php-log-subscribe';
// 打日志文件目录
const LOG_DIR               = '/data/logs/' . CURRENT_PROCESS_NAME . '/';
/********************************************* 常量定义 end *************************************************************/

// 保证进程唯一
uniqueProcess();

/********************************************* 逻辑 begin **************************************************************/
// 错误日志当前行数
$lastLine = file_exists(ERROR_LOG_PATH) ? count(file(ERROR_LOG_PATH)) : 0;
// 进程常驻,订阅错误日志
while (true) {
    sleep(2);
    handle($lastLine);
}

function handle(&$lastLine)
{
    logInfo('开始扫描错误日志');
    if (!file_exists(ERROR_LOG_PATH)) {
        logError('日志文件不存在');
    }

    // 当前行数
    $nowLine = file_exists(ERROR_LOG_PATH) ? count(file(ERROR_LOG_PATH)) : 0;

    // 如果行数没有变化,直接返回
    if ($nowLine === $lastLine) return true;

    // 如果当前行数小于上次的行数,说明是新的一天了,老的日志发生了压缩,生成了新的日志文件
    if ($nowLine < $lastLine) {
        $lastLine = $nowLine;
        // 当前日志文件为空
        if ($nowLine === 0) {
            logInfo('当前日志内容为空');
            return true;
        }
        // 如果新的日志文件中有内容了,告警
        $alarmMsg = cutFile(0, $nowLine - 1);
        if (!empty($alarmMsg)) {
            alarm($alarmMsg);
        }

        return true;
    }

    // 如果执行到这里,说明日志有了增量
    $alarmMsg = cutFile($lastLine, $nowLine - 1);
    $lastLine = $nowLine;
    if (!empty($alarmMsg)) {
        alarm($alarmMsg);
    }

    return true;
}

/**
 * 告警
 *
 * @param string $alarmMsg
 */
function alarm($alarmMsg = '')
{
    // TODO 邮件告警 or 飞书告警等,这里暂时用打日志方式来进行告警
    logWarning($alarmMsg);
}

/**
 * 截取指定文件内容
 *
 * @param $start
 * @param $end
 * @return string
 */
function cutFile($start, $end)
{
    if (!file_exists(ERROR_LOG_PATH)) {
        return '';
    }
    $f = new SplFileObject(ERROR_LOG_PATH,'r');
    $f->seek($start);
    $ret = '';
    for ($i = $start; $i <= $end; $i++) {
        $ret .= $f->current();
        $f->next();
    }
    return $ret;
}
/********************************************* 逻辑 end ****************************************************************/

/**
 * 保证进程的唯一性,防止进程不退出造成系统中存在多个相同的进程
 */
function uniqueProcess()
{
    // 当前进程名称
    $currentProcessName = CURRENT_PROCESS_NAME;
    // 设置当前进程名称
    cli_set_process_title($currentProcessName);
    // 当前进程 ID
    $pid = posix_getpid();
    // 除了当前进程 ID,杀掉其他同名进程,保证同一时刻进程的唯一性
    $exec = "ps aux | grep {$currentProcessName} | grep -v 'grep' | grep -v {$pid} | awk '{print $2}' | xargs kill -9";
    shell_exec($exec);
}

/********************************************* 日志相关方法 begin ********************************************************/
function logInfo($content)
{
    createLogDir();
    assembleContent($content);
    file_put_contents(LOG_DIR . 'info.log.' . date('Ymd'), $content . PHP_EOL, FILE_APPEND | LOCK_EX);
}

function logError($content)
{
    createLogDir();
    assembleContent($content);
    file_put_contents(LOG_DIR . 'error.log.' . date('Ymd'), $content . PHP_EOL, FILE_APPEND | LOCK_EX);
}

function logWarning($content)
{
    createLogDir();
    assembleContent($content);
    file_put_contents(LOG_DIR . 'warning.log.' . date('Ymd'), $content . PHP_EOL, FILE_APPEND | LOCK_EX);
}

function logSuccess($content)
{
    createLogDir();
    assembleContent($content);
    file_put_contents(LOG_DIR . 'success.log.' . date('Ymd'), $content . PHP_EOL, FILE_APPEND | LOCK_EX);
}

function logDebug($content)
{
    createLogDir();
    assembleContent($content);
    file_put_contents(LOG_DIR . 'debug.log.' . date('Ymd'), $content . PHP_EOL, FILE_APPEND | LOCK_EX);
}

function assembleContent(&$content)
{
    $now = date('Y-m-d H:i:s');
    $content = '' . $now . '' . $content . PHP_EOL;
}

function createLogDir()
{
    !is_dir(LOG_DIR) && @mkdir(LOG_DIR, 0777, true);
}
/********************************************* 日志相关方法 end **********************************************************/

后记:其实可以引入更专业的工具,比如 Sentry。

由于该脚本是常驻的,如何保证该进程不会挂掉?传送门:如何使用 PHP 脚本来保证其他进程的 KeepAlive?