
Monolog 日志重复输出(如每条日志被写入多次)的根本原因是:每次调用 pushHandler() 时都向同一个 Logger 实例添加新 Handler,而未清理旧 Handler,导致日志被重复处理。
monolog 日志重复输出(如每条日志被写入多次)的根本原因是:每次调用 `pushhandler()` 时都向同一个 logger 实例添加新 handler,而未清理旧 handler,导致日志被重复处理。
在 Monolog 中,Logger 实例默认支持多 Handler 复用——但这也意味着:若在单个 Logger 生命周期内反复调用 pushHandler()(如每次记录日志前都新增一个 StreamHandler),所有已注册的 Handler 都会响应同一条日志,从而造成重复写入。您原始代码中的 Log 类正是如此:$this->logger 是共享的单例对象,而 info()、debug() 等方法每次都调用 $this->logger->pushHandler(...),致使 Handler 数量随调用次数线性增长(第 1 次调用后 1 个 Handler,第 2 次后 2 个,第 3 次后 3 个……),最终每条日志被写入 N 次。
✅ 正确做法是:避免复用同一 Logger 实例并反复注入 Handler;改为按需创建轻量级 Logger 实例。Monolog 的 Logger 构造开销极小,且 StreamHandler 支持复用文件句柄,因此“每次日志操作新建 Logger”不仅安全,而且更清晰、可预测。
以下是优化后的专业实现(已适配 Monolog v3+,兼容 PHP 8.0+):
<?php
namespace Plugin\Core;
use Monolog\Handler\StreamHandler;
use Monolog\Level;
use Monolog\Logger;
use Monolog\Formatter\LineFormatter;
class Log
{
private string $path;
private string $name;
public function __construct(string $name = 'wpmatrimony', ?string $path = null)
{
$this->name = $name;
$this->path = $path ?? WPMATRIMONY_PLUGIN_DIR . './logs/' . $name . '.log';
}
private function createLogger(Level $level): Logger
{
$logger = new Logger($this->name);
$logger->setTimezone(new \DateTimeZone('Asia/Kolkata')); // ✅ 修正时区标识符(ASIA/KOLKATA → Asia/Kolkata)
$handler = new StreamHandler($this->path, $level);
$formatter = new LineFormatter(
format: null,
dateFormat: 'd-m-Y H:i:s',
allowInlineLineBreaks: false,
ignoreEmptyContextAndExtra: true
);
$handler->setFormatter($formatter);
$logger->pushHandler($handler);
return $logger;
}
public function debug(string $message, array $context = []): void
{
$this->createLogger(Level::Debug)->debug($message, $context);
}
public function info(string $message, array $context = []): void
{
$this->createLogger(Level::Info)->info($message, $context);
}
public function warning(string $message, array $context = []): void
{
$this->createLogger(Level::Warning)->warning($message, $context);
}
public function error(string $message, array $context = []): void
{
$this->createLogger(Level::Error)->error($message, $context);
}
// 其他方法(notice/critical/alert/emergency)依此类推,均调用 createLogger()
}? 关键改进说明:
-
无状态设计:
Log类本身不持有Logger实例,彻底规避 Handler 积累问题; -
时区标准化:
Asia/Kolkata是 IANA 标准时区名(原ASIA/KOLKATA非法,会导致DateTimeZone构造失败); - 类型安全:显式声明参数/返回类型(PHP 7.4+),增强 IDE 支持与运行时可靠性;
-
上下文支持:为日志方法增加
$context参数(如['user_id' => 123]),便于结构化日志追踪; -
资源友好:
StreamHandler内部自动管理文件句柄,高频调用下仍保持高效。
⚠️ 注意事项:
- 若需全局统一日志配置(如同时输出到文件 + Slack),应在应用初始化阶段一次性构建完整 Logger,而非在工具类中动态拼装;
- 不建议在高并发场景(如 WordPress 主循环)中频繁实例化
Log对象——可考虑将其注册为容器服务或使用静态单例(但必须确保 Handler 无状态); - 日志路径需确保目录存在且 Web 服务器有写权限,建议在构造函数中加入
wp_mkdir_p(dirname($this->path))安全创建。
通过该方案,您将获得稳定、可维护、符合 PSR-3 规范的日志行为——每条日志严格对应一次物理写入,彻底告别“越记越多”的陷阱。

















