monolog.yaml 中 formatter 必须配置在具体 handler 下而非顶层,否则报错或无效;json 格式需用 monolog.formatter.json 或自定义类;捕获日志数组须用 abstractprocessinghandler;db 日志须 try/catch 防 500。

monolog.yaml 里 formatter 必须挂在 handler 下,不是写在顶层
改日志格式后发现内容还是纯文本、或报 InvalidConfigurationException,八成是把 formatter: monolog.formatter.json 写错了位置。它只能出现在某个具体 handler 的配置块里,比如 file 或 rotated_file 下,不能放在 monolog: 根节点下,也不能放在 handlers: 列表外面。
常见错误写法:
monolog:
formatter: monolog.formatter.json # ❌ 错:顶层无效
handlers:
file:
type: rotating_file
path: '%kernel.logs_dir%/app.log'
正确写法:
monolog:
handlers:
file:
type: rotating_file
path: '%kernel.logs_dir%/app.log'
level: debug
formatter: monolog.formatter.json # ✅ 对:紧贴 handler
channels: ['main']
-
monolog.formatter.json是 Symfony 提供的默认 JSON 格式器服务 ID,无需额外注册 - 如果用了
fingers_crossed这类 wrapper handler,formatter要写在被包装的 handler(如stream)里,而不是 wrapper 本身 - 开发环境若
kernel.debug: false,debug级别日志会被直接丢弃,即使 formatter 正确也看不到输出
想加 trace 或内存信息?得写自定义 JsonFormatter 类
默认 JsonFormatter 输出字段固定:message、context、level_name、channel、datetime,不带调用栈或内存用量。要加这些,必须继承并重写 format() 方法。
新建 src/Log/CustomJsonFormatter.php:
namespace App\Log;
<p>use Monolog\Formatter\JsonFormatter;</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2491" title="Symfony Linux版"><img
src="https://img.php.cn/upload/zhuanqu/000/000/086/6a5f5ae8906a0613.png" alt="Symfony Linux版" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/gongju/2491" title="Symfony Linux版" class="overflowclass">Symfony Linux版</a>
<p class="overflowclass">Symfony Linux版整理 Symfony CLI 5.17.1 官方下载入口和 Symfony 框架安装配置说明。</p>
</div>
<a rel="nofollow" href="/xiazai/gongju/2491" title="Symfony Linux版" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><p>class CustomJsonFormatter extends JsonFormatter
{
public function format(array $record): string
{
$record['memory'] = memory_get_usage(true);
if (isset($record['context']['exception'])) {
$record['trace'] = $record['context']['exception']->getTraceAsString();
}
// 时区统一为 UTC,去掉微秒
$record['datetime'] = $record['datetime']->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d\TH:i:sO');</p><pre class="brush:php;toolbar:false;"> return parent::format($record);
}}
- 在
services.yaml中声明服务:App\Log\CustomJsonFormatter: ~ - 在
monolog.yaml中引用:formatter: 'App\Log\CustomJsonFormatter' - 注意:不要在
format()里做耗时操作(如 DB 查询),否则拖慢所有日志写入
要实时拿到日志数组?别碰 Formatter,得写 AbstractProcessingHandler
目标不是“改输出格式”,而是“运行时捕获日志数据为 PHP 数组”,比如发告警、存审计、打点统计——这时候 Formatter 完全没用,因为它是序列化阶段,输出已是字符串。必须实现 Monolog\Handler\AbstractProcessingHandler。
示例 InMemoryArrayHandler:
namespace App\Log;
<p>use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Logger;</p><p>class InMemoryArrayHandler extends AbstractProcessingHandler
{
private static array $records = [];</p><pre class="brush:php;toolbar:false;">protected function write(array $record): void
{
self::$records[] = $record;
}
public static function getRecords(): array
{
return self::$records;
}
public static function clear(): void
{
self::$records = [];
}}
- 在
services.yaml中加标签:tags: [{ name: 'monolog.handler' }] - 在
monolog.yaml中启用:handler: 'App\Log\InMemoryArrayHandler',并设好channels和level - 调用
InMemoryArrayHandler::getRecords()就能取到原始$record数组,含完整上下文和异常对象 - 注意:别在生产环境全局启用,尤其不能让这个 handler 处理
event或doctrine频道,否则数组爆炸式增长
数据库日志写入必须带 try/catch,否则请求会挂
用 Doctrine 写日志到数据库,最常踩的坑不是“插不进去”,而是“插失败导致整个 HTTP 请求 500”。Monolog 的 write() 在同步模式下抛异常,会中断当前执行流。
- 所有
$entityManager->flush()必须包在try/catch(\Exception)里,失败时降级用error_log()或静默丢弃 - 避免循环日志:DB handler 自身出错时,不能再触发同通道日志(否则死循环),建议设独立
db_errorchannel - 高并发下考虑异步:用 Messenger 发消息到
log.write总线,由消费者处理,但延迟和运维成本会上升 - 字段长度要留余量:
context字段存 JSON,MySQL 建表至少用TEXT,PostgreSQL 用JSONB
真正上线前,得确认单次 DB 日志写入是否引入 >20ms 额外延迟,以及连接池满时是否批量失败——这些不会在本地 dev 环境暴露出来。










