Как стать автором
Обновить

Laravel трюки: автоматическое подключение каналов логирования

Уровень сложностиСредний
Время на прочтение3 мин
Количество просмотров5.3K

Всем привет! Меня зовут Иван Шишкин и я руковожу разработкой в агентстве Intensa.

В этой статье хотел бы поделиться методом автоматического подключения каналов логирования в Laravel через механизм сервис контейнеров(DI).

Рассказываю и показываю в статье
Рассказываю и показываю в статье

Логирование в Laravel

Фреймворк имеет на борту мощный функционал логирования, реализованный на базе библиотеки Monolog и для удобства клиентского кода скрыт за фасадом Log.

В этой статье я не буду углубляться в устройство и возможности функционала логирования, для этого есть прекрасная документация.

Определение проблемы

Обычно для сбора логов в классах мы используем следующую конструкцию:

<?php

namespace App\Http\Controllers;

class TaskController extends Controller
{
    protected LoggerInterface $logger;

    public function __construct()
    {
        $this->logger = Log::channel('task');
    }
}

Сохраняем экземпляр LoggerInterface конкретного канала логирования в свойство класса.

Такой подход имеет место быть, если:
— у вас небольшой проект,
— логирование введено в класс временно,
— вы не используете DI в проекте.

В остальных случаях такая реализация даст сильную связанность ваших классов с конкретным каналом логирования. И, когда нам понадобиться изменить поведение узла логирования, например, поменять канал логирования, придётся прыгать по классам и переписывать код. При большом количестве классов в проекте это может вызвать у вас боль и страдания.

Автоматическое подключение

Перед тем как начать описание, еще раз подчеркну — в нашем мире мало магии и PHP не исключение. Для того, чтобы автоматическое подключение сработало, классы вашего проекта должны быть получены через DI.

Первым делом нужно создать интерфейс-маркер, к которому мы привяжем реализацию. Интерфейс должен наследовать от LoggerAwareInterface.

<?php

namespace App\Support\Logging;

use Psr\Log\LoggerAwareInterface;

interface DefaultLoggerAwareInterface extends LoggerAwareInterface
{

}

Теперь в провайдере свяжем данный интерфейс с нужным каналом логирования при помощи метода afterResolving.

<?php

namespace App\Providers;

class AppServiceProvider extends ServiceProvider
{
	public function register(): void
  {
        $this->app->afterResolving(
            DefaultLoggerAwareInterface::class,
            function (DefaultLoggerAwareInterface $aware) {
								// Привязываем канал "common".
								// Log::channel вернет экземпляр \Psr\Log\LoggerInterface
                $aware->setLogger(Log::channel('common'));
            }
        );
   }
}

Переходим к подключению нашего логгера к классу. Для демонстрации возьму контроллер из предыдущего блока.

<?php

namespace App\Http\Controllers;

use App\Support\Logging\DefaultLoggerAwareInterface;

class TaskController extends Controller implements DefaultLoggerAwareInterface
{
    use LoggerAwareTrait;
}

Имплементируем интерфейс DefaultLoggerAwareInterface и подключаем трейт Psr\Log\LoggerAwareTrait, чтобы наш класс получил свойство $this->logger.

Логируем нужные данные в нашем классе:

<?php

namespace App\Http\Controllers;

use App\Support\Logging\DefaultLoggerAwareInterface;
use Illuminate\Http\Request;

class TaskController extends Controller implements DefaultLoggerAwareInterface
{
    use LoggerAwareTrait;

		public function __invoke(Request $request)
		{
			//...
			$this->logger->info('Данные запроса', $request->toArray());
		}
}

Для безопасного вызова логгера я бы рекомендовал использовать Null-safe оператор перед вызовом метода логирования. Это исключит фатальные ошибки в вашем коде, в случае, если по какой-то причине логгер не был доставлен в ваш класс.

$this->logger?->info('Данные запроса', $request->toArray());

Без магии

Также есть вариант пробрасывать логгеры в ваши классы через автоматическое связывание(Autowiring).

Передаем аргумент LoggerInterface в метод вашего класса

<?php

namespace App\Http\Controllers;

use App\Support\Logging\DefaultLoggerAwareInterface;
use Illuminate\Http\Request;

class TaskController extends Controller
{
    
		public function __invoke(Request $request, LoggerInterface $logger)
		{
			//...
			$this->logger->info('Данные запроса', $request->toArray());
		}
}

В AppServiceProvider определим правило для сервис контейнера. Через данную запись мы явно определяем, что если, класс TaskController запросил у DI экземпляр LoggerInterface, то нужно вернуть канал логирования с кодом ‘common’

<?php

namespace App\Providers;

class AppServiceProvider extends ServiceProvider
{
	public function register(): void
  {
				$this->app->when(TaskController::class)
            ->needs(LoggerInterface::class)
            ->give(fn() => Log::channel('common'));
   }
}

Эпилог

В завершение хочу отметить, что описанное в статье не является панацеей и правилом из категории “Right Way”. Я бы назвал это трюком, который поможет сделать ваш код менее связанным и поддерживаемым в перспективе.

Теги:
Хабы:
Всего голосов 10: ↑9 и ↓1+11
Комментарии5

Публикации

Истории

Работа

PHP программист
96 вакансий

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань