You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 06:47:54