tp5中需自定义轻量、异步、可配置的告警函数(如notify_alert),明确触发条件、接收方和可靠发送方式,避免同步阻塞;须显式传入脱敏上下文,不替代异常处理机制,且自身出错需降级写入error_log。

TP5 里没有“告警函数”这个内置概念,所谓自定义告警,本质是把异常、错误或业务条件触发时的响应逻辑封装成可复用的函数,再在合适位置调用。核心不是写一个叫 alarm() 的函数,而是明确“什么情况下要告警”“告警发给谁”“怎么发才可靠”。
如何在 TP5 中封装一个基础告警函数
告警函数通常用于捕获非致命但需人工介入的场景(比如支付回调验签失败、库存扣减超限),不建议替代异常处理机制。它应轻量、可配置、不阻塞主流程。
- 放在
application/common.php中最直接,全局可用 - 函数名建议带业务前缀,如
notify_alert(),避免和框架/扩展冲突 - 必须支持异步发送(如用
exec("nohup php think notify:send ... > /dev/null 2>&1 &")或队列),否则 HTTP 请求会卡住 - 参数至少包含:
$level(error/warning/info)、$message、$context(数组,含uid、order_id等上下文)
示例:
function notify_alert($level, $message, $context = []) {
$log = sprintf('[%s] %s | %s', strtoupper($level), $message, json_encode($context, JSON_UNESCAPED_UNICODE));
error_log($log . "\n", 3, RUNTIME_PATH . 'logs/alert.log');
// 异步调用命令行任务(需提前定义 think 命令)
if ($level === 'error') {
exec("nohup php " . ROOT_PATH . "think notify:send --level={$level} --msg=" . escapeshellarg($message) . " --context=" . escapeshellarg(json_encode($context)) . " > /dev/null 2>&1 &");
}
}
为什么不能直接在 controller 里用 mail() 或 file_put_contents 告警
同步调用会导致接口响应变慢甚至超时,尤其在高并发下极易雪崩。TP5 的请求生命周期短,且多数生产环境禁用了 mail() 函数或限制了 SMTP 连接数。
-
mail()是阻塞式,DNS 查询 + SMTP 握手可能耗时数百毫秒 -
file_put_contents()写日志没问题,但“告警”意味着人要看到——纯文件不通知,等于没告 - 若用
curl同步推送企业微信/钉钉,网络抖动会直接拖垮接口 - 正确做法:只记日志 + 推送消息到队列(如 Redis List)或命令行后台任务
如何让告警带上请求上下文(如 URL、UID、IP)
静态函数无法自动感知当前请求,必须显式传入。TP5 的 Request 对象不能跨生命周期保存,所以不要试图在函数里调用 \think\Request::instance() —— 它在命令行任务中会返回 null。
- controller 或中间件中提前收集:
$context = ['url' => request()->url(), 'ip' => request()->ip(), 'uid' => session('uid')]; - 把
$context当作参数传给notify_alert(),而不是在函数内部尝试重建 - 敏感字段(如手机号、token)必须脱敏后再写入日志或通知内容,例如
substr($phone, 0, 3) . '****' . substr($phone, -4) - 避免在
$context中传大对象(如整个$_POST),防止日志爆炸或内存溢出
告警函数和 TP5 异常处理器的关系
二者职责不同,不可互相替代。异常处理器(exception_handle 配置项指向的类)负责捕获未被捕获的 Exception 和 Error,是兜底;而告警函数是主动上报,属于业务监控范畴。
- 在自定义
ApiHandleException的render()方法里,可以调用notify_alert('error', ...)上报未预期异常 - 但不要在
notify_alert()里 throw 新异常,否则可能引发二次错误 - 如果用了
set_error_handler()捕获 PHP 警告,也建议只记录,不调用告警函数——E_NOTICE/E_DEPRECATED 不值得告警 - 真正该告警的,是
E_USER_ERROR、trigger_error(..., E_USER_WARNING)或自定义的BusinessException
最易被忽略的一点:告警函数本身出错(比如 Redis 连接失败、日志目录不可写)不能静默吞掉——至少要 fallback 到 error_log() 原生写入,否则你会彻底失去告警能力。











