Laravel自定义JSON日志stdout前缀带WARN问题求助
问题描述
我基于Laravel实现了自定义JSON日志格式化器,将日志输出到php://stdout时,终端日志会额外带上WARN前缀(示例:WARN {"method":"GET","uri":"/_health","route":null}),但输出到文件时是纯JSON(示例:{"method":"GET","uri":"/_health","route":null})。业务需要纯JSON日志给监控服务解析,但这个前缀导致日志被识别为字符串无法解析。另外,日志级别明明设的是debug,但所有日志都只带WARN关键字。用Laravel内置的JsonFormatter输出到stdout也有同样问题。
相关代码
CustomJsonFormatter.php
<?php namespace App\Logging; use Monolog\Formatter\JsonFormatter; use Monolog\LogRecord; class CustomJsonFormatter extends JsonFormatter { public function format(LogRecord $record): string { $formatted = $record['context']; if ($this->includeStacktraces) { $formatted['stack_trace'] = $record['formatted']['exception']; } return $this->toJson($formatted, true) . ($this->appendNewline ? "\n" : ''); } }
MethodLogChannel.php
<?php namespace App\Logging; use Monolog\Logger; use Monolog\Handler\StreamHandler; use Monolog\Formatter\JsonFormatter; use App\Logging\CustomJsonFormatter; use Monolog\Processor\IntrospectionProcessor; use Monolog\Processor\WebProcessor; class MethodLogChannel { public function __invoke(array $config) { $log = new Logger($config['name']); $handler = new StreamHandler($config['with']['path'], $config['with']['level']); $handler->setFormatter(new CustomJsonFormatter()); $log->pushHandler($handler); $log->pushProcessor(new IntrospectionProcessor()); $log->pushProcessor(new WebProcessor($_SERVER)); return $log; } }
logging.php中的channel配置
'methodlog' => [ 'driver' => 'custom', 'via' => App\Logging\MethodLogChannel::class, 'formatter' => App\Logging\CustomJsonFormatter::class, 'name' => 'methodlog', 'with' => [ 'path' => 'php://stdout', 'level' => env('LOG_LEVEL', 'debug'), ], ]
解决方案
1. 修复日志级别参数错误
问题核心之一是给StreamHandler传的日志级别是字符串(比如debug),但Monolog需要的是常量值。字符串会被默认解析为Logger::WARNING,这就是所有日志都带WARN前缀的原因。
修改MethodLogChannel.php中的Handler初始化代码:
<?php namespace App\Logging; use Monolog\Logger; use Monolog\Handler\StreamHandler; use App\Logging\CustomJsonFormatter; use Monolog\Processor\IntrospectionProcessor; use Monolog\Processor\WebProcessor; class MethodLogChannel { public function __invoke(array $config) { $log = new Logger($config['name']); // 把字符串级别转成Monolog常量 $levelName = strtolower($config['with']['level']); $level = match ($levelName) { 'debug' => Logger::DEBUG, 'info' => Logger::INFO, 'notice' => Logger::NOTICE, 'warning' => Logger::WARNING, 'error' => Logger::ERROR, 'critical' => Logger::CRITICAL, 'alert' => Logger::ALERT, 'emergency' => Logger::EMERGENCY, default => Logger::DEBUG, }; $handler = new StreamHandler($config['with']['path'], $level); $handler->setFormatter(new CustomJsonFormatter()); $log->pushHandler($handler); $log->pushProcessor(new IntrospectionProcessor()); $log->pushProcessor(new WebProcessor($_SERVER)); return $log; } }
2. 确保Formatter输出纯JSON
优化自定义Formatter,把日志级别、时间戳这些关键信息加入JSON,同时保证输出无额外前缀:
<?php namespace App\Logging; use Monolog\Formatter\JsonFormatter; use Monolog\LogRecord; class CustomJsonFormatter extends JsonFormatter { public function format(LogRecord $record): string { $formatted = $record['context']; // 加入日志级别和时间戳到JSON $formatted['level'] = $record['level_name']; $formatted['timestamp'] = $record['datetime']->format('Y-m-d H:i:s'); if ($this->includeStacktraces && isset($record['formatted']['exception'])) { $formatted['stack_trace'] = $record['formatted']['exception']; } return $this->toJson($formatted, true) . ($this->appendNewline ? "\n" : ''); } }
3. 调整日志配置避免冲突
在logging.php中简化自定义channel配置,去掉多余的formatter项(已在Channel类中设置),可设置为默认通道:
// logging.php 'default' => env('LOG_CHANNEL', 'methodlog'), 'methodlog' => [ 'driver' => 'custom', 'via' => App\Logging\MethodLogChannel::class, 'name' => 'methodlog', 'with' => [ 'path' => env('LOG_PATH', 'php://stdout'), 'level' => env('LOG_LEVEL', 'debug'), ], ]
4. 测试验证
调用日志方法测试:
\Log::channel('methodlog')->debug('Health check', ['method' => 'GET', 'uri' => '/_health', 'route' => null]);
终端应输出纯JSON:
{"method":"GET","uri":"/_health","route":null,"level":"DEBUG","timestamp":"2024-05-20 14:30:00"}
内容的提问来源于stack exchange,提问作者Priyam Sinha
相关产品推荐
相关产品推荐

