Skip to content

Commit

Permalink
Add auto_excluded_channels configuration option
Browse files Browse the repository at this point in the history
  • Loading branch information
HypeMC committed Feb 28, 2023
1 parent 0e136c5 commit 5bd9dd9
Show file tree
Hide file tree
Showing 7 changed files with 340 additions and 4 deletions.
79 changes: 79 additions & 0 deletions DependencyInjection/Compiler/AutoExcludedChannelsPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\MonologBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
* Excludes all specified channels from handlers with a non-exclusive channel list.
* Needs to run before {@see LoggerChannelPass}.
*/
class AutoExcludedChannelsPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if ($autoExcludedChannels = array_values($container->getParameter('monolog.auto_excluded_channels'))) {
$this->processChannels($container, $autoExcludedChannels);
}

$container->getParameterBag()->remove('monolog.auto_excluded_channels');
}

private function processChannels(ContainerBuilder $container, array $autoExcludedChannels): void
{
$processedHandlers = [];

/** @var array<string, ?array{type: string, elements: list<string>}> $handlersToChannels */
$handlersToChannels = $container->getParameter('monolog.handlers_to_channels');

foreach ($handlersToChannels as $id => &$handlersToChannel) {
if (isset($handlersToChannel['type']) && 'exclusive' !== $handlersToChannel['type']) {
continue;
}

$handlerName = substr($id, 16); // remove "monolog.handler."

if (null === $handlersToChannel) {
$handlersToChannel = [
'type' => 'exclusive',
'elements' => $autoExcludedChannels,
];
$processedHandlers[$handlerName] = $autoExcludedChannels;

continue;
}

foreach ($autoExcludedChannels as $autoExcludedChannel) {
if (false !== $index = array_search('!'.$autoExcludedChannel, $handlersToChannel['elements'], true)) {
array_splice($handlersToChannel['elements'], $index, 1);
if (!$handlersToChannel['elements']) {
$handlersToChannel = null;
}
} elseif (!\in_array($autoExcludedChannel, $handlersToChannel['elements'], true)) {
$handlersToChannel['elements'][] = $autoExcludedChannel;
$processedHandlers[$handlerName][] = $autoExcludedChannel;
}
}
}

$container->setParameter('monolog.handlers_to_channels', $handlersToChannels);

foreach ($processedHandlers as $handlerName => $excludedChannels) {
$container->log($this, sprintf(
'Auto-excluded the following channels from the "%s" handler: "%s".',
$handlerName,
implode('", "', $excludedChannels)
));
}
}
}
5 changes: 5 additions & 0 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -385,13 +385,18 @@ public function getConfigTreeBuilder()

$handlers = $rootNode
->fixXmlConfig('channel')
->fixXmlConfig('auto_excluded_channel')
->fixXmlConfig('handler')
->children()
->scalarNode('use_microseconds')->defaultTrue()->end()
->arrayNode('channels')
->canBeUnset()
->prototype('scalar')->end()
->end()
->arrayNode('auto_excluded_channels')
->canBeUnset()
->prototype('scalar')->end()
->end()
->arrayNode('handlers');

$handlers
Expand Down
1 change: 1 addition & 0 deletions DependencyInjection/MonologExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public function load(array $configs, ContainerBuilder $container)
}

$container->setParameter('monolog.additional_channels', isset($config['channels']) ? $config['channels'] : []);
$container->setParameter('monolog.auto_excluded_channels', $config['auto_excluded_channels'] ?? []);

if (method_exists($container, 'registerForAutoconfiguration')) {
if (interface_exists(ProcessorInterface::class)) {
Expand Down
11 changes: 7 additions & 4 deletions MonologBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
use Monolog\Formatter\JsonFormatter;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\HandlerInterface;
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\AddProcessorsPass;
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\AddSwiftMailerTransportPass;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\LoggerChannelPass;
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\AutoExcludedChannelsPass;
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\DebugHandlerPass;
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\AddProcessorsPass;
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\FixEmptyLoggerPass;
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\LoggerChannelPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

/**
* @author Jordi Boggiano <[email protected]>
Expand All @@ -31,6 +33,7 @@ public function build(ContainerBuilder $container)
{
parent::build($container);

$container->addCompilerPass(new AutoExcludedChannelsPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 10);
$container->addCompilerPass($channelPass = new LoggerChannelPass());
if (!class_exists('Symfony\Bridge\Monolog\Processor\DebugProcessor') || !class_exists('Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddDebugLogProcessorPass')) {
$container->addCompilerPass(new DebugHandlerPass($channelPass));
Expand Down
1 change: 1 addition & 0 deletions Resources/config/schema/monolog-1.0.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="handler" type="handler" />
<xsd:element name="channel" type="xsd:string" />
<xsd:element name="auto-excluded-channel" type="xsd:string" />
</xsd:choice>
</xsd:complexType>

Expand Down
203 changes: 203 additions & 0 deletions Tests/DependencyInjection/Compiler/AutoExcludedChannelsPassTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\MonologBundle\Tests\DependencyInjection\Compiler;

use PHPUnit\Framework\TestCase;
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\AutoExcludedChannelsPass;
use Symfony\Bundle\MonologBundle\MonologBundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class AutoExcludedChannelsPassTest extends TestCase
{
/**
* @group legacy
*
* @dataProvider handlerChannels
*/
public function testProcess(?array $autoExcludedChannels, array $handlers, ?array $expectedChannels, array $expectedLog): void
{
$container = new ContainerBuilder();

$bundle = new MonologBundle();
$container->registerExtension($bundle->getContainerExtension());
$bundle->build($container);

$container->loadFromExtension('monolog', [
'channels' => ['channel1', 'channel2', 'channel3', 'channel4'],
'auto_excluded_channels' => $autoExcludedChannels,
'handlers' => $handlers,
]);

$container->compile();

$this->assertSame($expectedChannels, $container->getParameter('monolog.handlers_to_channels'));
$this->assertFalse($container->hasParameter('monolog.auto_excluded_channels'));

$this->assertSame($expectedLog, array_values(array_filter(
$container->getCompiler()->getLog(),
function (string $log) {
return 0 === strpos($log, AutoExcludedChannelsPass::class);
}
)));
}

public static function handlerChannels(): iterable
{
yield 'No auto-excluded channels' => [
null,
[
'foo' => [
'type' => 'console',
'channels' => ['!channel1'],
],
],
['monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel1']]],
[],
];
yield 'Empty auto-excluded channels array' => [
[],
[
'foo' => [
'type' => 'console',
'channels' => ['!channel1'],
],
],
['monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel1']]],
[],
];

yield 'No channels' => [
['channel1'],
[
'foo' => [
'type' => 'console',
'channels' => null,
],
],
['monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel1']]],
[self::getLog('foo', ['channel1'])],
];
yield 'Empty channels array' => [
['channel1'],
[
'foo' => [
'type' => 'console',
'channels' => [],
],
],
['monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel1']]],
[self::getLog('foo', ['channel1'])],
];

yield 'Inclusive' => [
['channel2'],
[
'foo' => [
'type' => 'console',
'channels' => ['channel1'],
],
],
['monolog.handler.foo' => ['type' => 'inclusive', 'elements' => ['channel1']]],
[],
];

yield 'Exclusive without exception' => [
['channel2'],
[
'foo' => [
'type' => 'console',
'channels' => ['!channel1'],
],
],
['monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel1', 'channel2']]],
[self::getLog('foo', ['channel2'])],
];
yield 'Exclusive with exception' => [
['channel2'],
[
'foo' => [
'type' => 'console',
'channels' => ['!channel1', '!!channel2'],
],
],
['monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel1']]],
[],
];
yield 'Exclusive with only an exception' => [
['channel1'],
[
'foo' => [
'type' => 'console',
'channels' => ['!!channel1'],
],
],
['monolog.handler.foo' => null],
[],
];

yield 'Explicitly excluded' => [
['channel1'],
[
'foo' => [
'type' => 'console',
'channels' => ['!channel1'],
],
],
['monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel1']]],
[],
];

yield 'Multiple auto-excluded channels' => [
['channel1', 'channel3'],
[
'foo' => [
'type' => 'console',
'channels' => ['!channel2'],
],
],
['monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel2', 'channel1', 'channel3']]],
[self::getLog('foo', ['channel1', 'channel3'])],
];

yield 'Multiple handlers' => [
['channel1', 'channel3'],
[
'foo' => [
'type' => 'console',
'channels' => ['!channel2'],
],
'bar' => [
'type' => 'console',
'channels' => ['channel1', 'channel4'],
],
'baz' => [
'type' => 'console',
'channels' => ['!!channel1', '!channel2'],
],
],
[
'monolog.handler.baz' => ['type' => 'exclusive', 'elements' => ['channel2', 'channel3']],
'monolog.handler.bar' => ['type' => 'inclusive', 'elements' => ['channel1', 'channel4']],
'monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel2', 'channel1', 'channel3']],
],
[
self::getLog('baz', ['channel3']),
self::getLog('foo', ['channel1', 'channel3']),
],
];
}

private static function getLog(string $handler, array $channels): string
{
return sprintf('%s: Auto-excluded the following channels from the "%s" handler: "%s".', AutoExcludedChannelsPass::class, $handler, implode('", "', $channels));
}
}
44 changes: 44 additions & 0 deletions Tests/MonologBundleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\MonologBundle\Tests;

use PHPUnit\Framework\TestCase;
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\AutoExcludedChannelsPass;
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\LoggerChannelPass;
use Symfony\Bundle\MonologBundle\MonologBundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class MonologBundleTest extends TestCase
{
/**
* @group legacy
*/
public function testAutoExcludedChannelsPassIsRegisteredWithCorrectPriority()
{
$container = new ContainerBuilder();

(new MonologBundle())->build($container);

$compilerPassIndexes = [];
foreach ($container->getCompilerPassConfig()->getBeforeOptimizationPasses() as $i => $compilerPass) {
$compilerPassIndexes[\get_class($compilerPass)] = $i;
}

$this->assertArrayHasKey(LoggerChannelPass::class, $compilerPassIndexes);
$this->assertArrayHasKey(AutoExcludedChannelsPass::class, $compilerPassIndexes);

$this->assertGreaterThan(
$compilerPassIndexes[AutoExcludedChannelsPass::class],
$compilerPassIndexes[LoggerChannelPass::class]
);
}
}

0 comments on commit 5bd9dd9

Please sign in to comment.