В прошлой статье о логах я рассказывал про PSR-3 и обещал показать свою реализацию CombineLogger. Выполняю обещание 🔥
🎯 Откуда взялся CombineLogger?
В проектах, над которыми я работаю, везде используется Psr\Log\LoggerInterface. Это стандарт, который позволяет не привязываться к конкретной библиотеке.
Но возникла проблема: интерфейс один, а логгеров нужно несколько.
Хочется писать в файл и отправлять в output или в GrayLog. А код везде принимает только один LoggerInterface.
Добавлять везде echo - не хорошо!
Решение: создать класс, который сам реализует Psr\Log\LoggerInterface, а внутри держит массив логгеров и передаёт вызов каждому.
📦 Код
<?php
namespace App\Infrastructure\Logger;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
class CombineLogger implements LoggerInterface
{
private array $loggers = [];
// Простые, но удобные методы :)
public function addLogger(LoggerInterface $logger): void
{
$this->loggers[] = $logger;
}
public function log($level, $message, array $context = []): void
{
foreach ($this->loggers as $logger) {
$logger->log($level, $message, $context);
}
}
// Реализуем все по LoggerInterface
public function emergency($message, array $context = []): void
{
$this->log(LogLevel::EMERGENCY, $message, $context);
}
public function alert($message, array $context = []): void
{
$this->log(LogLevel::ALERT, $message, $context);
}
public function critical($message, array $context = []): void
{
$this->log(LogLevel::CRITICAL, $message, $context);
}
public function error($message, array $context = []): void
{
$this->log(LogLevel::ERROR, $message, $context);
}
public function warning($message, array $context = []): void
{
$this->log(LogLevel::WARNING, $message, $context);
}
public function notice($message, array $context = []): void
{
$this->log(LogLevel::NOTICE, $message, $context);
}
public function info($message, array $context = []): void
{
$this->log(LogLevel::INFO, $message, $context);
}
public function debug($message, array $context = []): void
{
$this->log(LogLevel::DEBUG, $message, $context);
}
}
🎯 Как это использовать
Собираем логгеры в одном месте (например, в контейнере зависимостей):
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// Логгер в файл
$fileLogger = new Logger('app');
$fileLogger->pushHandler(new StreamHandler('/var/log/app.log'));
// Простой логгер для отладки (выводит в консоль)
$outputLogger = new Logger('output');
$outputLogger->pushHandler(new StreamHandler('php://output'));
$combineLogger = new CombineLogger();
$combineLogger->addLogger($fileLogger);
$combineLogger->addLogger($outputLogger);
// Везде в коде используем $combineLogger как обычный PSR-3 логгер
$combineLogger->error('Ошибка оплаты', ['order_id' => 123]);
👍 Что даёт этот подход:
- Единый интерфейс: везде используется PSR-3, код не знает, сколько логгеров внутри
-
Гибкость: добавил новый канал = дописал
addLogger()в одном месте - Простота: не нужно в каждом классе думать, куда логировать, не нужно править код вне инициализации логгера
🧠 Развитие идеи: стратегии
Простая цепочка вызовов - это хорошо, но можно пойти дальше.
Что если нужен не вызов всех логгеров, а другая логика? Например:
- Резервный логгер: первый логгер упал - вызываем второй
- Логгер по условию: в зависимости от уровня или контекста выбираем разные каналы
- Асинхронный логгер: пишем в очередь, а не блокируем основной поток
interface LoggerStrategyInterface
{
public function log(array $loggers, $level, $message, array $context): void;
}
class ChainStrategy implements LoggerStrategyInterface
{
public function log(array $loggers, $level, $message, array $context): void
{
foreach ($loggers as $logger) {
$logger->log($level, $message, $context);
}
}
}
class FallbackStrategy implements LoggerStrategyInterface
{
public function log(array $loggers, $level, $message, array $context): void
{
foreach ($loggers as $logger) {
try {
$logger->log($level, $message, $context);
return; // Успешно — выходим
} catch (\Throwable $e) {
continue; // Пробуем следующий
}
}
}
}
Тогда CombineLogger становится ещё гибче:
class CombineLogger implements LoggerInterface
{
private array $loggers = [];
private LoggerStrategyInterface $strategy;
public function __construct(LoggerStrategyInterface $strategy)
{
$this->strategy = $strategy;
}
public function log($level, $message, array $context = []): void
{
$this->strategy->log($this->loggers, $level, $message, $context);
}
// остальные методы...
}
📌 PSR-3 + CombineLogger + Стратегии = код, который не боится любых требований к логированию.
💬 Обсудить пост:
- Telegram → https://t.me/buriy_dev
- ВКонтакте → https://vk.com/buriy_dev
- Max → https://max.ru/id616507661604_biz
🔥 И не забудь подписаться :)