public function __invoke(array $record): array {的庖丁解牛
·
public function __invoke(array $record): array 是 PHP 中 Monolog 日志处理器(Processor) 的标准接口方法,属于 “可调用对象”(Callable Object) 模式。
它允许将对象作为函数调用,在日志记录流程中动态修改或 enrich 日志上下文(如注入 trace_id、用户 ID、环境信息)。
一、语言特性:__invoke() 的本质
🔑 可调用对象(Callable Object)
- PHP 魔术方法
__invoke():- 使对象能被当作函数调用;
$obj()等价于$obj->__invoke();
- 签名要求:
- 参数与返回值任意,但 Monolog 约定为
array → array;
- 参数与返回值任意,但 Monolog 约定为
📜 示例:通用可调用对象
class Greeter {
public function __invoke(string $name): string {
return "Hello, $name!";
}
}
$greet = new Greeter();
echo $greet("Alice"); // "Hello, Alice!" (对象被当函数调用)
💡 核心价值:将行为封装为对象,同时保持函数式调用的简洁性。
二、Monolog 集成:Processor 的工作流
🌐 Monolog 日志处理流程
📦 $record 结构(Monolog 标准)
[
'message' => 'User login',
'context' => ['user_id' => 123], // 业务上下文
'level' => 200, // 日志级别(INFO=200)
'level_name' => 'INFO',
'channel' => 'app',
'datetime' => DateTimeImmutable,
'extra' => [] // Processor 可在此添加字段
]
✅ Processor 典型实现:注入 trace_id
use Monolog\Processor\ProcessorInterface;
class TraceIdProcessor implements ProcessorInterface {
public function __invoke(array $record): array {
// 从全局上下文获取 trace_id(如 $_SERVER)
$traceId = $_SERVER['HTTP_X_REQUEST_ID'] ?? null;
if ($traceId) {
$record['extra']['trace_id'] = $traceId;
}
return $record; // 必须返回修改后的记录
}
}
// 注册到 Logger
$logger->pushProcessor(new TraceIdProcessor());
🔑 关键规则:
__invoke()必须返回$record,否则日志链中断。
3. 工程价值:为什么用 Processor 而非手动记录?
| 场景 | 手动记录 | Processor |
|---|---|---|
| 注入 trace_id | 每次 $logger->info(..., ['trace_id'=>$id]) |
自动注入,零侵入 |
| 添加用户 ID | 重复传参 | 全局自动 enrich |
| 过滤敏感字段 | 人工检查 | 统一清洗逻辑 |
| 多环境标记 | 硬编码 env=prod |
自动读取配置 |
🛡️ 核心优势:
- 解耦:业务代码不关心日志 enrich 逻辑;
- 一致性:所有日志自动含
trace_id,无遗漏; - 可维护:修改 enrich 逻辑只需改 Processor;
四、最佳实践:生产级 Processor 设计
✅ 1. 实现 ProcessorInterface(类型安全)
use Monolog\Processor\ProcessorInterface;
class ContextEnricher implements ProcessorInterface {
public function __invoke(array $record): array {
// enrich logic
return $record;
}
}
✅ 2. 避免副作用(Pure Function)
- 不修改全局状态;
- 不抛出异常(会中断日志);
- 失败时静默(如
trace_id不存在则跳过);
✅ 3. 结构化字段(便于日志系统解析)
// ✅ 推荐:放入 'extra'
$record['extra']['trace_id'] = $id;
// ❌ 避免:污染 'context'
$record['context']['trace_id'] = $id; // 可能与业务 context 冲突
✅ 4. 性能敏感(Processor 在每次日志调用时执行)
- 避免 I/O 操作(如数据库查询);
- 缓存全局值(如
env);
✅ 5. 注册时机
- 全局 Logger 初始化时注册:
$logger = new Logger('app'); $logger->pushProcessor(new TraceIdProcessor()); $logger->pushProcessor(new UserIdProcessor());
五、高危误区
🚫 误区 1:“Processor 可用于异步处理”
- 真相:
- Processor 同步执行,阻塞日志调用;
- 异步需用 Handler(如
BufferHandler);
🚫 误区 2:“返回 null 不影响日志”
- 真相:
return null→ 后续 Handler 收到 null → 日志丢失;- 必须
return $record;
🚫 误区 3:“可在 Processor 中修改日志级别”
- 真相:
- 技术可行,但破坏日志语义;
- 应通过 Handler 过滤级别(如
FilterHandler);
六、终极心法:Processor 是日志的“中间件”
不要把日志当静态记录,
而要当可编程的数据流。
- 无 Processor:
- 日志是扁平的、无上下文的;
- 有 Processor:
- 日志是 enriched 的、可关联的、可分析的;
- 结果:
- 前者用于“看”,后者用于“查”。
真正的可观测性,
不在“记录多少”,
而在“关联多深”。
七、行动建议:今日 Processor 实践
## 2025-07-10 Processor 实践
### 1. 创建 TraceIdProcessor
- [ ] 实现 __invoke() 注入 HTTP_X_REQUEST_ID
### 2. 注册到 Logger
- [ ] $logger->pushProcessor(new TraceIdProcessor())
### 3. 验证日志输出
- [ ] 检查日志 JSON 含 "extra.trace_id"
### 4. 扩展 UserIdProcessor
- [ ] 从 Auth::id() 注入 user_id
✅ 完成即构建上下文日志能力。
当你停止手动传 trace_id,
开始用 Processor 自动 enrich,
日志就从文本,
变为可追踪的数据。
这,才是专业 PHP 工程师的可观测性观。
更多推荐



所有评论(0)