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
📜 示例:通用可调用对象
class Greeter {
    public function __invoke(string $name): string {
        return "Hello, $name!";
    }
}

$greet = new Greeter();
echo $greet("Alice"); // "Hello, Alice!" (对象被当函数调用)

💡 核心价值将行为封装为对象,同时保持函数式调用的简洁性


二、Monolog 集成:Processor 的工作流

🌐 Monolog 日志处理流程
Handler Processor Logger 应用代码 Handler Processor Logger 应用代码 $logger->>info('msg', ['user_id'=>123]) __invoke(['message'=>'msg', 'context'=>['user_id'=>123]]) 返回 enriched 记录 写入文件/发送到 Datadog
📦 $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 工程师的可观测性观。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐