Skip to content

Commit 5bd9dd9

Browse files
committed
Add auto_excluded_channels configuration option
1 parent 0e136c5 commit 5bd9dd9

File tree

7 files changed

+340
-4
lines changed

7 files changed

+340
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\MonologBundle\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
17+
/**
18+
* Excludes all specified channels from handlers with a non-exclusive channel list.
19+
* Needs to run before {@see LoggerChannelPass}.
20+
*/
21+
class AutoExcludedChannelsPass implements CompilerPassInterface
22+
{
23+
public function process(ContainerBuilder $container): void
24+
{
25+
if ($autoExcludedChannels = array_values($container->getParameter('monolog.auto_excluded_channels'))) {
26+
$this->processChannels($container, $autoExcludedChannels);
27+
}
28+
29+
$container->getParameterBag()->remove('monolog.auto_excluded_channels');
30+
}
31+
32+
private function processChannels(ContainerBuilder $container, array $autoExcludedChannels): void
33+
{
34+
$processedHandlers = [];
35+
36+
/** @var array<string, ?array{type: string, elements: list<string>}> $handlersToChannels */
37+
$handlersToChannels = $container->getParameter('monolog.handlers_to_channels');
38+
39+
foreach ($handlersToChannels as $id => &$handlersToChannel) {
40+
if (isset($handlersToChannel['type']) && 'exclusive' !== $handlersToChannel['type']) {
41+
continue;
42+
}
43+
44+
$handlerName = substr($id, 16); // remove "monolog.handler."
45+
46+
if (null === $handlersToChannel) {
47+
$handlersToChannel = [
48+
'type' => 'exclusive',
49+
'elements' => $autoExcludedChannels,
50+
];
51+
$processedHandlers[$handlerName] = $autoExcludedChannels;
52+
53+
continue;
54+
}
55+
56+
foreach ($autoExcludedChannels as $autoExcludedChannel) {
57+
if (false !== $index = array_search('!'.$autoExcludedChannel, $handlersToChannel['elements'], true)) {
58+
array_splice($handlersToChannel['elements'], $index, 1);
59+
if (!$handlersToChannel['elements']) {
60+
$handlersToChannel = null;
61+
}
62+
} elseif (!\in_array($autoExcludedChannel, $handlersToChannel['elements'], true)) {
63+
$handlersToChannel['elements'][] = $autoExcludedChannel;
64+
$processedHandlers[$handlerName][] = $autoExcludedChannel;
65+
}
66+
}
67+
}
68+
69+
$container->setParameter('monolog.handlers_to_channels', $handlersToChannels);
70+
71+
foreach ($processedHandlers as $handlerName => $excludedChannels) {
72+
$container->log($this, sprintf(
73+
'Auto-excluded the following channels from the "%s" handler: "%s".',
74+
$handlerName,
75+
implode('", "', $excludedChannels)
76+
));
77+
}
78+
}
79+
}

DependencyInjection/Configuration.php

+5
Original file line numberDiff line numberDiff line change
@@ -385,13 +385,18 @@ public function getConfigTreeBuilder()
385385

386386
$handlers = $rootNode
387387
->fixXmlConfig('channel')
388+
->fixXmlConfig('auto_excluded_channel')
388389
->fixXmlConfig('handler')
389390
->children()
390391
->scalarNode('use_microseconds')->defaultTrue()->end()
391392
->arrayNode('channels')
392393
->canBeUnset()
393394
->prototype('scalar')->end()
394395
->end()
396+
->arrayNode('auto_excluded_channels')
397+
->canBeUnset()
398+
->prototype('scalar')->end()
399+
->end()
395400
->arrayNode('handlers');
396401

397402
$handlers

DependencyInjection/MonologExtension.php

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ public function load(array $configs, ContainerBuilder $container)
100100
}
101101

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

104105
if (method_exists($container, 'registerForAutoconfiguration')) {
105106
if (interface_exists(ProcessorInterface::class)) {

MonologBundle.php

+7-4
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
use Monolog\Formatter\JsonFormatter;
1515
use Monolog\Formatter\LineFormatter;
1616
use Monolog\Handler\HandlerInterface;
17+
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\AddProcessorsPass;
1718
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\AddSwiftMailerTransportPass;
18-
use Symfony\Component\HttpKernel\Bundle\Bundle;
19-
use Symfony\Component\DependencyInjection\ContainerBuilder;
20-
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\LoggerChannelPass;
19+
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\AutoExcludedChannelsPass;
2120
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\DebugHandlerPass;
22-
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\AddProcessorsPass;
2321
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\FixEmptyLoggerPass;
22+
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\LoggerChannelPass;
23+
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
24+
use Symfony\Component\DependencyInjection\ContainerBuilder;
25+
use Symfony\Component\HttpKernel\Bundle\Bundle;
2426

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

36+
$container->addCompilerPass(new AutoExcludedChannelsPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 10);
3437
$container->addCompilerPass($channelPass = new LoggerChannelPass());
3538
if (!class_exists('Symfony\Bridge\Monolog\Processor\DebugProcessor') || !class_exists('Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddDebugLogProcessorPass')) {
3639
$container->addCompilerPass(new DebugHandlerPass($channelPass));

Resources/config/schema/monolog-1.0.xsd

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<xsd:choice minOccurs="0" maxOccurs="unbounded">
1212
<xsd:element name="handler" type="handler" />
1313
<xsd:element name="channel" type="xsd:string" />
14+
<xsd:element name="auto-excluded-channel" type="xsd:string" />
1415
</xsd:choice>
1516
</xsd:complexType>
1617

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\MonologBundle\Tests\DependencyInjection\Compiler;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\AutoExcludedChannelsPass;
16+
use Symfony\Bundle\MonologBundle\MonologBundle;
17+
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
19+
class AutoExcludedChannelsPassTest extends TestCase
20+
{
21+
/**
22+
* @group legacy
23+
*
24+
* @dataProvider handlerChannels
25+
*/
26+
public function testProcess(?array $autoExcludedChannels, array $handlers, ?array $expectedChannels, array $expectedLog): void
27+
{
28+
$container = new ContainerBuilder();
29+
30+
$bundle = new MonologBundle();
31+
$container->registerExtension($bundle->getContainerExtension());
32+
$bundle->build($container);
33+
34+
$container->loadFromExtension('monolog', [
35+
'channels' => ['channel1', 'channel2', 'channel3', 'channel4'],
36+
'auto_excluded_channels' => $autoExcludedChannels,
37+
'handlers' => $handlers,
38+
]);
39+
40+
$container->compile();
41+
42+
$this->assertSame($expectedChannels, $container->getParameter('monolog.handlers_to_channels'));
43+
$this->assertFalse($container->hasParameter('monolog.auto_excluded_channels'));
44+
45+
$this->assertSame($expectedLog, array_values(array_filter(
46+
$container->getCompiler()->getLog(),
47+
function (string $log) {
48+
return 0 === strpos($log, AutoExcludedChannelsPass::class);
49+
}
50+
)));
51+
}
52+
53+
public static function handlerChannels(): iterable
54+
{
55+
yield 'No auto-excluded channels' => [
56+
null,
57+
[
58+
'foo' => [
59+
'type' => 'console',
60+
'channels' => ['!channel1'],
61+
],
62+
],
63+
['monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel1']]],
64+
[],
65+
];
66+
yield 'Empty auto-excluded channels array' => [
67+
[],
68+
[
69+
'foo' => [
70+
'type' => 'console',
71+
'channels' => ['!channel1'],
72+
],
73+
],
74+
['monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel1']]],
75+
[],
76+
];
77+
78+
yield 'No channels' => [
79+
['channel1'],
80+
[
81+
'foo' => [
82+
'type' => 'console',
83+
'channels' => null,
84+
],
85+
],
86+
['monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel1']]],
87+
[self::getLog('foo', ['channel1'])],
88+
];
89+
yield 'Empty channels array' => [
90+
['channel1'],
91+
[
92+
'foo' => [
93+
'type' => 'console',
94+
'channels' => [],
95+
],
96+
],
97+
['monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel1']]],
98+
[self::getLog('foo', ['channel1'])],
99+
];
100+
101+
yield 'Inclusive' => [
102+
['channel2'],
103+
[
104+
'foo' => [
105+
'type' => 'console',
106+
'channels' => ['channel1'],
107+
],
108+
],
109+
['monolog.handler.foo' => ['type' => 'inclusive', 'elements' => ['channel1']]],
110+
[],
111+
];
112+
113+
yield 'Exclusive without exception' => [
114+
['channel2'],
115+
[
116+
'foo' => [
117+
'type' => 'console',
118+
'channels' => ['!channel1'],
119+
],
120+
],
121+
['monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel1', 'channel2']]],
122+
[self::getLog('foo', ['channel2'])],
123+
];
124+
yield 'Exclusive with exception' => [
125+
['channel2'],
126+
[
127+
'foo' => [
128+
'type' => 'console',
129+
'channels' => ['!channel1', '!!channel2'],
130+
],
131+
],
132+
['monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel1']]],
133+
[],
134+
];
135+
yield 'Exclusive with only an exception' => [
136+
['channel1'],
137+
[
138+
'foo' => [
139+
'type' => 'console',
140+
'channels' => ['!!channel1'],
141+
],
142+
],
143+
['monolog.handler.foo' => null],
144+
[],
145+
];
146+
147+
yield 'Explicitly excluded' => [
148+
['channel1'],
149+
[
150+
'foo' => [
151+
'type' => 'console',
152+
'channels' => ['!channel1'],
153+
],
154+
],
155+
['monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel1']]],
156+
[],
157+
];
158+
159+
yield 'Multiple auto-excluded channels' => [
160+
['channel1', 'channel3'],
161+
[
162+
'foo' => [
163+
'type' => 'console',
164+
'channels' => ['!channel2'],
165+
],
166+
],
167+
['monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel2', 'channel1', 'channel3']]],
168+
[self::getLog('foo', ['channel1', 'channel3'])],
169+
];
170+
171+
yield 'Multiple handlers' => [
172+
['channel1', 'channel3'],
173+
[
174+
'foo' => [
175+
'type' => 'console',
176+
'channels' => ['!channel2'],
177+
],
178+
'bar' => [
179+
'type' => 'console',
180+
'channels' => ['channel1', 'channel4'],
181+
],
182+
'baz' => [
183+
'type' => 'console',
184+
'channels' => ['!!channel1', '!channel2'],
185+
],
186+
],
187+
[
188+
'monolog.handler.baz' => ['type' => 'exclusive', 'elements' => ['channel2', 'channel3']],
189+
'monolog.handler.bar' => ['type' => 'inclusive', 'elements' => ['channel1', 'channel4']],
190+
'monolog.handler.foo' => ['type' => 'exclusive', 'elements' => ['channel2', 'channel1', 'channel3']],
191+
],
192+
[
193+
self::getLog('baz', ['channel3']),
194+
self::getLog('foo', ['channel1', 'channel3']),
195+
],
196+
];
197+
}
198+
199+
private static function getLog(string $handler, array $channels): string
200+
{
201+
return sprintf('%s: Auto-excluded the following channels from the "%s" handler: "%s".', AutoExcludedChannelsPass::class, $handler, implode('", "', $channels));
202+
}
203+
}

Tests/MonologBundleTest.php

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\MonologBundle\Tests;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\AutoExcludedChannelsPass;
16+
use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\LoggerChannelPass;
17+
use Symfony\Bundle\MonologBundle\MonologBundle;
18+
use Symfony\Component\DependencyInjection\ContainerBuilder;
19+
20+
class MonologBundleTest extends TestCase
21+
{
22+
/**
23+
* @group legacy
24+
*/
25+
public function testAutoExcludedChannelsPassIsRegisteredWithCorrectPriority()
26+
{
27+
$container = new ContainerBuilder();
28+
29+
(new MonologBundle())->build($container);
30+
31+
$compilerPassIndexes = [];
32+
foreach ($container->getCompilerPassConfig()->getBeforeOptimizationPasses() as $i => $compilerPass) {
33+
$compilerPassIndexes[\get_class($compilerPass)] = $i;
34+
}
35+
36+
$this->assertArrayHasKey(LoggerChannelPass::class, $compilerPassIndexes);
37+
$this->assertArrayHasKey(AutoExcludedChannelsPass::class, $compilerPassIndexes);
38+
39+
$this->assertGreaterThan(
40+
$compilerPassIndexes[AutoExcludedChannelsPass::class],
41+
$compilerPassIndexes[LoggerChannelPass::class]
42+
);
43+
}
44+
}

0 commit comments

Comments
 (0)