|
@@ -68,6 +68,13 @@ class Timer extends Command
|
|
|
*/
|
|
*/
|
|
|
protected function execute(Input $input, Output $output)
|
|
protected function execute(Input $input, Output $output)
|
|
|
{
|
|
{
|
|
|
|
|
+ // PHP 8.1+ 会把「浮点隐式转 int 丢失精度」作为 E_DEPRECATED 抛出,
|
|
|
|
|
+ // ThinkPHP 的错误处理会把它升级为 ErrorException 导致 worker 进程直接退出。
|
|
|
|
|
+ // 该告警不影响实际计算(仅截断小数),定时任务链路里暂时屏蔽,避免进程反复挂掉。
|
|
|
|
|
+ error_reporting(E_ALL & ~E_DEPRECATED);
|
|
|
|
|
+ // worker 进程内的未捕获异常不会走 ThinkPHP 的异常渲染,堆栈会丢失,
|
|
|
|
|
+ // 这里统一落盘到 runtime/timer_error.log,便于定位崩溃位置。
|
|
|
|
|
+ $this->registerErrorLogger();
|
|
|
$this->init($input, $output);
|
|
$this->init($input, $output);
|
|
|
// 创建定时器任务
|
|
// 创建定时器任务
|
|
|
$worker = new Worker();
|
|
$worker = new Worker();
|
|
@@ -75,12 +82,84 @@ class Timer extends Command
|
|
|
$worker->runAll();
|
|
$worker->runAll();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 注册未捕获异常/致命错误日志(worker 场景下堆栈默认会被吞掉)
|
|
|
|
|
+ */
|
|
|
|
|
+ private function registerErrorLogger(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $logFile = runtime_path() . 'timer_error.log';
|
|
|
|
|
+ set_error_handler($this->makeErrorHandler($logFile));
|
|
|
|
|
+ set_exception_handler(function (\Throwable $e) use ($logFile) {
|
|
|
|
|
+ $this->writeErrorLog($logFile, sprintf(
|
|
|
|
|
+ "%s %s: %s in %s:%d\n%s\n",
|
|
|
|
|
+ date('Y-m-d H:i:s'),
|
|
|
|
|
+ get_class($e),
|
|
|
|
|
+ $e->getMessage(),
|
|
|
|
|
+ $e->getFile(),
|
|
|
|
|
+ $e->getLine(),
|
|
|
|
|
+ $e->getTraceAsString()
|
|
|
|
|
+ ));
|
|
|
|
|
+ });
|
|
|
|
|
+ register_shutdown_function(function () use ($logFile) {
|
|
|
|
|
+ $error = error_get_last();
|
|
|
|
|
+ if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true)) {
|
|
|
|
|
+ $this->writeErrorLog($logFile, sprintf(
|
|
|
|
|
+ "%s [fatal] %s in %s:%d\n",
|
|
|
|
|
+ date('Y-m-d H:i:s'),
|
|
|
|
|
+ $error['message'],
|
|
|
|
|
+ $error['file'],
|
|
|
|
|
+ $error['line']
|
|
|
|
|
+ ));
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 自定义错误处理器:告警/弃用类错误直接落盘并吞掉(返回 true),
|
|
|
|
|
+ * 阻止其被 ThinkPHP 升级为 ErrorException 打崩 worker 进程。
|
|
|
|
|
+ * 相同位置重复报错做去重(首次 + 每 500 次记录一条),避免日志刷屏。
|
|
|
|
|
+ */
|
|
|
|
|
+ private function makeErrorHandler(string $logFile): \Closure
|
|
|
|
|
+ {
|
|
|
|
|
+ return function (int $no, string $str, string $file = '', int $line = 0) use ($logFile): bool {
|
|
|
|
|
+ static $seen = [];
|
|
|
|
|
+ $key = md5($no . $str . $file . $line);
|
|
|
|
|
+ $seen[$key] = ($seen[$key] ?? 0) + 1;
|
|
|
|
|
+ if ($seen[$key] === 1 || $seen[$key] % 500 === 0) {
|
|
|
|
|
+ $this->writeErrorLog($logFile, sprintf(
|
|
|
|
|
+ "%s [%d] %s in %s:%d (累计 %d 次)\n",
|
|
|
|
|
+ date('Y-m-d H:i:s'),
|
|
|
|
|
+ $no,
|
|
|
|
|
+ $str,
|
|
|
|
|
+ $file,
|
|
|
|
|
+ $line,
|
|
|
|
|
+ $seen[$key]
|
|
|
|
|
+ ));
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 写入错误日志文件
|
|
|
|
|
+ * @param string $logFile
|
|
|
|
|
+ * @param string $content
|
|
|
|
|
+ */
|
|
|
|
|
+ private function writeErrorLog(string $logFile, string $content): void
|
|
|
|
|
+ {
|
|
|
|
|
+ @file_put_contents($logFile, $content . PHP_EOL, FILE_APPEND);
|
|
|
|
|
+ echo $content . PHP_EOL;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 定时器执行的内容
|
|
* 定时器执行的内容
|
|
|
* @return false|int
|
|
* @return false|int
|
|
|
*/
|
|
*/
|
|
|
public function start()
|
|
public function start()
|
|
|
{
|
|
{
|
|
|
|
|
+ // workerman runAll 会重设错误处理器,这里在 worker 进程内重新挂回,
|
|
|
|
|
+ // 确保告警类错误只落盘、不再升级为 ErrorException。
|
|
|
|
|
+ set_error_handler($this->makeErrorHandler(runtime_path() . 'timer_error.log'));
|
|
|
// 每隔n秒执行一次
|
|
// 每隔n秒执行一次
|
|
|
return $this->timer = \Workerman\Lib\Timer::add($this->interval, function () {
|
|
return $this->timer = \Workerman\Lib\Timer::add($this->interval, function () {
|
|
|
try {
|
|
try {
|
|
@@ -88,7 +167,15 @@ class Timer extends Command
|
|
|
echo 'timer...' . PHP_EOL;
|
|
echo 'timer...' . PHP_EOL;
|
|
|
Event::trigger('StoreTask');
|
|
Event::trigger('StoreTask');
|
|
|
} catch (\Throwable $e) {
|
|
} catch (\Throwable $e) {
|
|
|
- echo 'ERROR: ' . $e->getMessage() . PHP_EOL;
|
|
|
|
|
|
|
+ $this->writeErrorLog(runtime_path() . 'timer_error.log', sprintf(
|
|
|
|
|
+ "%s %s: %s in %s:%d\n%s\n",
|
|
|
|
|
+ date('Y-m-d H:i:s'),
|
|
|
|
|
+ get_class($e),
|
|
|
|
|
+ $e->getMessage(),
|
|
|
|
|
+ $e->getFile(),
|
|
|
|
|
+ $e->getLine(),
|
|
|
|
|
+ $e->getTraceAsString()
|
|
|
|
|
+ ));
|
|
|
$this->stop();
|
|
$this->stop();
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|