Log::info('email.sent', ['to' => $user->email, 'type' => 'welcome']);是 Laravel 中结构化日志(Structured Logging)的典型用法。它不只是“写一行日志”,而是通过上下文数据实现可搜索、可聚合、可告警的日志体系。
一、核心组件拆解
1.Log::info()
- Laravel 门面(Facade) 封装
Psr\Log\LoggerInterface - 等价于:
app('log')->info('email.sent',['to'=>'...']);
2.'email.sent'(消息模板)
- 非纯文本,而是事件标识符(Event Identifier)
- 作用:
- 机器可读(用于日志聚合)
- 人类可理解(表示“邮件已发送”事件)
3.['to' => ..., 'type' => ...](上下文数据)
- 结构化字段(Structured Context),非字符串拼接
- 优势:
- 可被日志系统(如 ELK、Datadog)自动解析为字段
- 支持
WHERE to = 'a@example.com'查询
二、底层执行流程
1.日志通道选择
- Laravel 默认通道:
stack(组合通道)- 开发环境:
single(写入storage/logs/laravel.log) - 生产环境:
daily+errorlog或monolog第三方驱动
- 开发环境:
2.Monolog 处理
- Monolog(Laravel 底层日志库) 将日志转换为
LogRecord对象:['level'=>200,// INFO'message'=>'email.sent','context'=>['to'=>'a@example.com','type'=>'welcome'],'extra'=>[...],// 请求 ID、时间等]
3.格式化输出
- 默认格式(
LineFormatter):[2025-06-15 10:00:00] production.INFO: email.sent {"to":"a@example.com","type":"welcome"} [] - JSON 格式(生产推荐):
{"level":"info","message":"email.sent","context":{"to":"a@example.com","type":"welcome"},"datetime":"2025-06-15T10:00:00+00:00","channel":"production"}
三、为什么用结构化日志?(vs 传统拼接)
❌ 传统日志(反模式)
Log::info("Email sent to{$user->email}(type: welcome)");// 输出: Email sent to a@example.com (type: welcome)- 问题:
- 无法精确查询
to = 'a@example.com' - 邮件地址含特殊字符时解析失败
- 无法聚合统计(如“每种邮件类型的发送量”)
- 无法精确查询
✅ 结构化日志(正模式)
Log::info('email.sent',['to'=>$user->email,'type'=>'welcome']);- 优势:
- 精确查询:
context.to:"a@example.com" - 聚合分析:
COUNT BY context.type - 自动告警:
context.type:welcom(拼写错误检测)
- 精确查询:
四、生产环境最佳实践
1.字段命名规范
- 使用 snake_case:
user_id而非userId - 避免动态键名:
// ❌ 危险:字段名动态变化Log::info('event',[$dynamicKey=>$value]);// ✅ 安全:固定字段Log::info('event',['key'=>$dynamicKey,'value'=>$value]);
2.敏感数据过滤
- 自动脱敏(通过 Monolog 处理器):
// config/logging.php'processors'=>[newMonolog\Processor\MaskProcessor(['password','token']),],- 日志中
password字段自动替换为***
- 日志中
3.关联请求上下文
- Laravel 自动注入:
request_id(用于链路追踪)user_id(若已认证)
- 自定义上下文:
Log::build()->pushContext('tenant_id',$tenantId)->info('email.sent',$context);
五、日志系统集成(ELK 示例)
1.Filebeat 采集
- 配置 Filebeat 解析 JSON 日志:
# filebeat.ymlprocessors:-decode_json_fields:fields:["message"]target:""
2.Kibana 查询
- 搜索特定用户邮件:
context.to:"a@example.com" - 统计邮件类型分布:
context.type:* → Visualize → Pie Chart
3.告警规则(ElastAlert)
- 规则:
context.type:"welcome" AND context.to.keyword NOT EXISTS- 触发:欢迎邮件未记录收件人 → 数据丢失
六、与 Sentry / Bugsnag 的区别
| 工具 | 用途 | 日志级别 |
|---|---|---|
| Laravel Log | 业务事件追踪(如邮件发送) | info,notice |
| Sentry | 异常监控(如 500 错误) | error,critical |
✅协同使用:
Log::info()记录预期行为Sentry::captureException()记录非预期错误
七、总结
| 维度 | 关键点 |
|---|---|
| 本质 | 结构化事件日志,非文本记录 |
| 核心价值 | 机器可读 + 可聚合 + 可告警 |
| 生产要求 | JSON 格式 + 字段规范 + 敏感过滤 |
| 反模式 | 字符串拼接 + 动态键名 + 敏感数据明文 |
日志不是“给人看的备忘录”,
而是“给系统分析的数据流”。
用Log::info('event', $context),
你写的不是日志,
而是可观测性的基石。