Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony autowiring monolog channels

Following this documentation, I can create many channels which will create services with the following name monolog.logger.<channel_name>

How can I inject these services into my service with DI injection and autowiring ?

class FooService
{
    public function __construct(LoggerInterface $loggerInterface) {  }
}

Yaml

#existing
foo_service:
    class: AppBundle\Services\FooService
    arguments: ["@monolog.logger.barchannel"]
# what I want to do
foo_service:
    autowire: true # how to inject @monolog.logger.barchannel ? 
like image 444
Fabien Papet Avatar asked May 04 '17 16:05

Fabien Papet


3 Answers

Starting from MonologBundle 3.5 you can autowire different Monolog channels by type-hinting your service arguments with the following syntax: Psr\Log\LoggerInterface $<channel>Logger. For example, to inject the service related to the app logger channel use this:

public function __construct(LoggerInterface $appLogger)
{
   $this->logger = $appLogger;
}

https://symfony.com/doc/current/logging/channels_handlers.html#monolog-autowire-channels

like image 199
fabmlk Avatar answered Nov 09 '22 13:11

fabmlk


I wrote (maybe more complicated) method. I don't want to tag my autowired services to tell symfony which channel to use. Using symfony 4 with php 7.1.

I built LoggerFactory with all additional channels defined in monolog.channels.

My factory is in bundle, so in Bundle.php add

$container->addCompilerPass(
    new LoggerFactoryPass(), 
    PassConfig::TYPE_BEFORE_OPTIMIZATION, 
    1
); // -1 call before monolog

This is important to call this compiler pass before monolog.bundle because monolog after pass removes parameters from container.

Now, LoggerFactoryPass

namespace Bundle\DependencyInjection\Compiler;


use Bundle\Service\LoggerFactory;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

class LoggerFactoryPass implements CompilerPassInterface
{

    /**
     * You can modify the container here before it is dumped to PHP code.
     * @param ContainerBuilder $container
     * @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
     */
    public function process(ContainerBuilder $container): void
    {
        if (!$container->has(LoggerFactory::class) || !$container->hasDefinition('monolog.logger')) {
            return;
        }

        $definition = $container->findDefinition(LoggerFactory::class);
        foreach ($container->getParameter('monolog.additional_channels') as $channel) {
            $loggerId = sprintf('monolog.logger.%s', $channel);
            $definition->addMethodCall('addChannel', [
                $channel,
                new Reference($loggerId)
            ]);
        }
    }
}

and LoggerFactory

namespace Bundle\Service;

use Psr\Log\LoggerInterface;

class LoggerFactory
{
    protected $channels = [];

    public function addChannel($name, $loggerObject): void
    {
        $this->channels[$name] = $loggerObject;
    }

    /**
     * @param string $channel
     * @return LoggerInterface
     * @throws \InvalidArgumentException
     */
    public function getLogger(string $channel): LoggerInterface
    {
        if (!array_key_exists($channel, $this->channels)) {
            throw new \InvalidArgumentException('You are trying to reach not defined logger channel');
        }

        return $this->channels[$channel];
    }
}

So, now you can inject LoggerFactory, and choose your channel

public function acmeAction(LoggerFactory $factory)
{
    $logger = $factory->getLogger('my_channel');
    $logger->log('this is awesome!');
}
like image 10
DrafFter Avatar answered Nov 09 '22 13:11

DrafFter


After some searching I have found some kind of workaround using tags and manually injecting several parameters to autowired service.

My answer looks similar to @Thomas-Landauer. The difference is, I do not have to manually create logger service, as the compiler pass from monolog bundle does this for me.

services:
    _defaults:
        autowire: true
        autoconfigure: true
    AppBundle\Services\FooService:
        arguments:
            $loggerInterface: '@logger'
        tags:
            - { name: monolog.logger, channel: barchannel }
like image 8
Tomasz Struczyński Avatar answered Nov 09 '22 14:11

Tomasz Struczyński