hyperf业务日志应通过自定义@log注解与around切面实现,需延迟组装日志、显式捕获异常、用applicationcontext获取协程上下文、配置maskfields脱敏、设priority≥100避免事务冲突,并控制日志开关与序列化性能。

Hyperf 的业务日志不能靠手动写 Logger::info() 堆出来,用 AOP 注解自动织入才是正解——但直接套用官方 @Before 或 @Around 容易漏参数、丢上下文、日志格式混乱,甚至引发协程上下文错乱。
为什么不能直接在切面里调 Logger::info()
Hyperf 默认日志驱动(如 Monolog)本身是协程安全的,但问题出在「日志内容构造时机」:如果在 @Before 中就拼接完整日志字符串,方法还没执行,就拿不到返回值;若改用 @AfterReturning,又可能因异常跳过,导致日志缺失。更隐蔽的是:Context::get() 在非主协程中可能读不到当前请求的 request_id 或用户 ID。
- 必须在
@Around中统一控制执行前后,并显式捕获异常 - 所有日志字段(入参、出参、耗时、异常堆栈)需延迟到方法执行完毕再组装
- 务必通过
ApplicationContext::get(ContainerInterface::class)->get(ContextInterface::class)获取当前协程上下文,而不是直接用静态Context::get()
@Log 注解定义和切面实现的关键细节
自定义注解不能只声明一个空类,得带上可配置字段,比如是否记录参数、是否脱敏、是否包含堆栈。切面类里也要区分同步/异步方法——对 Coroutine::create 启动的协程,Context 需要手动传递。
- 注解类必须加
@Annotation和@Target({Target::METHOD}),否则扫描不到 - 切面
process方法中,用$proceedingJoinPoint->getArguments()拿原始参数,别用$proceedingJoinPoint->getMethod()->getParameters()反射取名——它不包含实际传入值 - 记录耗时时,用
microtime(true)而非date('H:i:s'),避免跨秒误差 - 敏感字段(如
password、id_card)需按注解配置的maskFields=["password"]自动替换为"***"
/**
* @Annotation
* @Target({Target::METHOD})
*/
class Log
{
public $maskFields = [];
public $includeArgs = true;
public $includeResult = true;
}// 切面中序列化参数示例:
$args = $this->includeArgs ? $this->maskSensitive($joinPoint->getArguments(), $annotation->maskFields) : [];
如何避免日志刷屏和性能拖累
高频接口(如心跳、状态轮询)打满日志会迅速占满磁盘,且 JSON 序列化本身有开销。Hyperf 的 Logger 是异步写入,但切面里的序列化、字符串拼接、上下文提取全在主协程中同步执行。
- 加开关控制:注解默认关闭,只对
@Log(enabled=true)的方法生效 - 对
GET类查询接口,默认includeArgs=false,只记 URL 和耗时 - 避免在日志里
var_export($hugeArray, true)—— 改用json_encode($arr, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR)并限制深度 - 异常堆栈只取前 3 层(
explode("\n", $e->getTraceAsString(), 4)[0] ?? ''),防止单条日志超 1MB
最常被忽略的一点:AOP 切面的 priority 值会影响执行顺序。如果你同时用了 @Transaction 和 @Log,而 @Log 的 priority 太高(数值小),就会在事务开启前就记录日志,导致事务回滚后日志却已落盘。建议把 @Log 的 priority 设为 100 以上,确保它在事务、验证等前置切面之后运行。










