webman的自定义进程如何在进程停止的时候获取到异常信息

邓靖

问题描述

PHP: 8.1
workerman/webman-framework: v1.6.4
workerman/crontab: v1.0.6
webman/console: v1.3.0

目前项目是通过自定义进程来实现一个进程执行一个crontab(这个crontab的worker会new一个console执行run方法执行业务), 想在自定义进程的worker的onWorkerStop的时候进行一些企微告警

问题: webman自带的monitor监控进程会对所有worker的内存使用量进行监控, 如果发现超过memory_limit的80%的时候会主动的重启自定义进程, 但是自定义进程的代码有可能也是正常执行完毕了的(没有error或者exception), 就会触发我写的onWorkerStop的回调方法发送企微告警, 我希望在onWorkerStop的时候能知道前面的业务代码是否有error或者exception, 如果没有就不发送企微告警, 但是目前只能通过set_error_handler()拿到error, 拿不到异常(exception), 看框架源码应该是Worker在执行run的时候已经捕获了onWorkerStart()时的异常,进行了标准输出和日志记录, 所以set_exception_handler()获取不到任何异常

为此你搜索到了哪些方案及不适用的原因

  1. 自定义进程的基类中初始化时设置用户自定义错误处理(set_error_handler)和异常处理(set_exception_handler), 其中错误处理可以覆盖support/bootstrap.php中的错误处理, 但是我在这个onWorkerStop的时候无法获取到业务中是否出现了异常(exception)导致需要重启进程
<?php

namespace process\crontab;

use app\queue\redis\WebHookQiWeiBot;
use Workerman\Crontab\Crontab;
use Workerman\Worker;

class Base
{
    const RULE = ''; // 默认规则为空,由子类重写

    protected Crontab $crontab;
    /**
     * @var bool
     */
    protected bool $hasSendWorkerStopAlarm = false;

    private array $lastError = [];

    /**
     * 获取定时任务进程名称
     *
     * @return string
     */
    public static function processName(): string
    {
        return str_replace(' ', "-", sprintf('%s@%s', static::getRule(), static::class));
    }

    public function __construct()
    {
        set_error_handler(function ($errno, $errstr, $errfile, $errline) {
            $this->lastError = [
                'type' => $errno,
                'message' => $errstr,
                'file' => $errfile,
                'line' => $errline,
            ];
            print_r($this->lastError);
            if (error_reporting() & $errno) {
                throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
            }
        });
        register_shutdown_function(function() {
            $this->sendAlarm();
        });
    }

    /**
     * 获取定时任务的运行规则
     *
     * @return string
     */
    public static function getRule(): string
    {
        $rule = defined('static::RULE') ? static::RULE : ''; // 默认以代码中的规则为准
        $classNameArr = explode('\\', static::class);
        if ($ruleENV = getenv('CRONTAB_RULE_' . $classNameArr[array_key_last($classNameArr)])) {
            // 如果在配置中设置了定时任务规则, 则以配置中的为准
            $rule = $ruleENV;
        }
        return $rule;
    }

    /**
     * 进程退出回调
     *
     * @param Worker $worker
     * @return void
     */
    public function onWorkerStop(Worker $worker): void
    {
        $this->sendAlarm();
    }

    /**
     * 发送定时任务进程退出告警
     *
     * @return void
     */
    protected function sendAlarm(): void
    {
        if ($this->hasSendWorkerStopAlarm) return;
        // 获取到last_error并打印
        $error = error_get_last() ?: $this->lastError;
        $errorDetail = '';
        if ($error) {
            $errorTypeName = match ($error['type']) {
                E_ERROR             => 'E_ERROR',
                E_WARNING           => 'E_WARNING',
                E_PARSE             => 'E_PARSE',
                E_NOTICE            => 'E_NOTICE',
                E_CORE_ERROR        => 'E_CORE_ERROR',
                E_CORE_WARNING      => 'E_CORE_WARNING',
                E_COMPILE_ERROR     => 'E_COMPILE_ERROR',
                E_COMPILE_WARNING   => 'E_COMPILE_WARNING',
                E_USER_ERROR        => 'E_USER_ERROR',
                E_USER_WARNING      => 'E_USER_WARNING',
                E_USER_NOTICE       => 'E_USER_NOTICE',
                E_STRICT            => 'E_STRICT',
                E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
                E_DEPRECATED        => 'E_DEPRECATED',
                E_USER_DEPRECATED   => 'E_USER_DEPRECATED',
                default             => 'UNKNOWN',
            };
            $errorDetail = <<<EOT
类型: {$errorTypeName}
信息: {$error['message']}
文件: {$error['file']}
行号: {$error['line']}
EOT;
        }
        try {
            // (new WebHookQiWeiBot())->sendCrontabWorkerStoppedAlarm(static::processName(), $errorDetail);
            echo date('Y-m-d H:i:s') . " Worker " . static::processName() . " 发生错误退出: \n" . $errorDetail . PHP_EOL;
            $this->hasSendWorkerStopAlarm = true;
        } catch (\Throwable) {}
    }
}
127 2 0
2个回答

邓靖

已经用其他方案解决了

  • 暂无评论
ichynul

在业务层try catch 发通知,然后再throw出来

  • 邓靖 2026-03-05

    就是这样改的, 原有的业务代码都onWorkerStart换成了另外一个基类的抽象方法, 基类写一个onWorkerStart, 然后try catch这个抽象方法, 就是替换了100多处很多业务代码的方法名字

🔝