Symonony autowiring monolog channels
By following this documentation , I can create many channels that will create services with the following namemonolog.logger.<channel_name>
How can I inject these services into my service using DI injection and auto-wiring?
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 ?
source to share
I wrote a (maybe more complex) method. I don't want to tag my auto messaging services to tell Symfony which channel to use. Using Symfony 4 with PHP 7.1.
I created a LoggerFactory with all additional channels defined in monolog.channels.
My factory is bundled, so in Bundle.php add
$container->addCompilerPass(
new LoggerFactoryPass(),
PassConfig::TYPE_BEFORE_OPTIMIZATION,
1
); // -1 call before monolog
It is important to call this compiler pass before monolog.bundle because monolog after the pass removes the options from the 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 add LoggerFactory and select your channel
public function acmeAction(LoggerFactory $factory)
{
$logger = $factory->getLogger('my_channel');
$logger->log('this is awesome!');
}
source to share
After some searching, I found some kind of workaround using tags and manually entered a few parameters into the auto connect service.
My answer is similar to @ Thomas-Landauer's. The difference is that I don't have to manually create the registration service as the compiler goes from the monologue package for me.
services:
_defaults:
autowire: true
autoconfigure: true
AppBundle\Services\FooService:
arguments:
$loggerInterface: '@logger'
tags:
- { name: monolog.logger, channel: barchannel }
source to share
I have not found a way to auto-prepare the registration channel itself. However, I found a way to use autowire
in principle and only enter the log manually. With help, class FooService
it might look like services.yml
(Symfony 3.3):
# services.yml
services:
_defaults:
autowire: true
autoconfigure: true
AppBundle\Services\FooService:
arguments:
$loggerInterface: '@monolog.logger.barchannel'
So the "trick" is to explicitly introduce the registration channel, but all other dependencies of that service are injected through the autodevice.
source to share
You can use the bind parameter :
services:
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: true
bind:
$loggerMyApi: '@monolog.logger.my_api'
Then you can use it in your service constructor:
use Psr\Log\LoggerInterface;
...
public function __construct(LoggerInterface $loggerMyApi)
{
...
}
source to share
Basically, you have two options:
First, the labeling service:
services:
App\Log\FooLogger:
arguments: ['@logger']
tags:
- { name: monolog.logger, channel: foo }
Then you can use yours CustomLogger
as a dependency elsewhere
Second, you can rely on Monolog to automatically register loggers for each configurable channel in the configuration:
# config/packages/prod/monolog.yaml
monolog:
channels: ['foo', 'bar']
Then the following services will be available to you:, monolog.logger.foo
'monolog.logger.bar'
Then you can retrieve them from the service container or connect them manually, for example:
services:
App\Lib\MyService:
$fooLogger: ['@monolog.logger.foo']
source to share
I recently implemented single point access to all registered MonologBundle loggers. And also I tried to find the best solution - and made the automatically generated logger decorators. Each class decorates one object of one registered monologue channel.
Link to kit adrenaline / monologue-car rental-kit
source to share