12

按照本文档,我可以创建许多通道,这些通道将创建具有以下名称的服务monolog.logger.<channel_name>

如何通过 DI 注入和自动装配将这些服务注入到我的服务中?

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 ? 
4

9 回答 9

10

我写了(也许更复杂的)方法。我不想标记我的自动连接服务来告诉 symfony 使用哪个频道。 在 php 7.1 中使用 symfony 4。

我用 monolog.channels 中定义的所有附加通道构建了LoggerFactory

我的工厂是捆绑的,所以在Bundle.php添加

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

在monolog.bundle之前调用此编译器传递很重要,因为传递之后的 monolog 会从容器中删除参数。

现在,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)
            ]);
        }
    }
}

和 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];
    }
}

所以,现在你可以注入 LoggerFactory,并选择你的频道

public function acmeAction(LoggerFactory $factory)
{
    $logger = $factory->getLogger('my_channel');
    $logger->log('this is awesome!');
}
于 2018-05-29T09:41:17.580 回答
8

经过一番搜索,我发现了某种使用标签并手动将几个参数注入自动装配服务的解决方法。

我的回答类似于@Thomas-Landauer。不同之处在于,我不必手动创建记录器服务,因为 monolog 包中的编译器传递为我完成了这项工作。

services:
    _defaults:
        autowire: true
        autoconfigure: true
    AppBundle\Services\FooService:
        arguments:
            $loggerInterface: '@logger'
        tags:
            - { name: monolog.logger, channel: barchannel }
于 2017-12-18T11:07:47.540 回答
8

您可以使用绑定参数

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'

然后您可以在服务的构造函数中使用它:

use Psr\Log\LoggerInterface;
...
public function __construct(LoggerInterface $loggerMyApi)
{
...
}
于 2018-08-17T15:14:47.437 回答
8

从 MonologBu​​ndle 3.5 开始,您可以通过使用以下语法对服务参数进行类型提示来自动连接不同的 Monolog 通道Psr\Log\LoggerInterface $<channel>Logger:例如,要注入与应用记录器通道相关的服务,请使用以下命令:

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

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

于 2020-01-23T10:02:44.783 回答
5

我没有找到自动连接记录器通道的方法。但是,我找到了一种autowire 原则上使用的方法,并且只手动注入记录器。有了你的class FooService,这就是services.yml(Symfony 3.3)的样子:

# services.yml

services:
    _defaults:
        autowire: true
        autoconfigure: true
    AppBundle\Services\FooService:
        arguments:
            $loggerInterface: '@monolog.logger.barchannel'

所以“技巧”是显式地注入记录器通道,同时仍然通过自动装配注入该服务的所有其他依赖项。

于 2017-10-09T10:57:59.643 回答
0

本质上,您有两个选择:

一、服务标签:

services:
App\Log\FooLogger:
    arguments: ['@logger']
    tags:
        - { name: monolog.logger, channel: foo }

然后你可以在CustomLogger其他地方使用你的依赖

其次,您可以依靠 Monolog 为配置中的每个自定义通道自动注册记录器:

# config/packages/prod/monolog.yaml
monolog:
    channels: ['foo', 'bar']

然后,您将获得以下服务:monolog.logger.foo, 'monolog.logger.bar'

然后,您可以从服务容器中检索它们,或手动连接它们,例如:

services:
App\Lib\MyService:
    $fooLogger: ['@monolog.logger.foo']

你可以在这里这里阅读更多。

于 2018-10-05T12:33:29.600 回答
0

最近我正在通过 MonologBu​​ndle 实现对所有注册记录器的单点访问。而且我还尝试做一些更好的解决方案 - 并做了自动生成的记录器装饰器。每个类装饰一个已注册独白通道的一个对象。

链接到捆绑包adrenalinkin/monolog-autowire-bundle

于 2019-04-28T21:36:01.897 回答
0

对于那些仍在为此苦苦挣扎的人。在 Symfony 4.3 中,除此之外,我还为特定通道添加了一个别名,因为没有它,它只能在开发环境中工作:构建时,单元测试都失败了,因为自定义记录器是一个未定义的服务.

 monolog.logger.my_custom_logger:         
   alias: Psr\Log\LoggerInterface         
   public: true 

 App\Logger\MyLogger:         
   arguments:             
     $logger: '@monolog.logger.my_custom_logger'
于 2019-09-11T23:27:29.180 回答
0

文档中,现在可以根据参数名称的类型提示进行自动装配。

// autowires monolog with "foo" channel
public function __construct(\Psr\Log\LoggerInterface $fooLogger);
于 2020-05-20T13:42:28.367 回答