Ver código fonte

[MonologBundle] Refactored the configuration to allow adding handlers

A subsequent config file does not overwrite the whole stack anymore. An
handler can now be redefined using the same name and will keep its place
in the stack. A new handler will be added at the bottom of the stack due
to the way the config are merged.

Handlers of the stack now have a priority (defaulting to 0) which is used
to allow to reorder them. This allow to add an handler at the top of the
stack by giving it a higher priority. Handlers with the same priority stay
in the order where they are defined in the config files.
As the merging strategy of the Config component could change it is
recommended to use the priority when adding a new handler in a subsequent
file even to put it at the bottom.

The support of the new BufferHandler (type: buffer) and RotatingFileHandler
(type: rotatingfile) was also added.
Christophe Coevoet 14 anos atrás
pai
commit
4949e0d1ed

+ 10 - 8
src/Symfony/Bundle/MonologBundle/DependencyInjection/Configuration.php

@@ -41,9 +41,9 @@ class Configuration implements ConfigurationInterface
             ->children()
                 ->arrayNode('handlers')
                     ->canBeUnset()
-                    ->performNoDeepMerging()
                     ->useAttributeAsKey('name')
                     ->prototype('array')
+                        ->canBeUnset()
                         ->children()
                             ->scalarNode('type')
                                 ->isRequired()
@@ -54,19 +54,21 @@ class Configuration implements ConfigurationInterface
                                 ->end()
                             ->end()
                             ->scalarNode('id')->end()
+                            ->scalarNode('priority')->defaultValue(0)->end()
                             ->scalarNode('level')->defaultValue('DEBUG')->end()
                             ->booleanNode('bubble')->defaultFalse()->end()
-                            ->scalarNode('path')->end() // stream specific
-                            ->scalarNode('ident')->end() // syslog specific
-                            ->scalarNode('facility')->end() // syslog specific
-                            ->scalarNode('action_level')->end() // fingerscrossed specific
-                            ->scalarNode('buffer_size')->end() // fingerscrossed specific
-                            ->scalarNode('handler')->end() // fingerscrossed specific
+                            ->scalarNode('path')->end() // stream and rotating
+                            ->scalarNode('ident')->end() // syslog
+                            ->scalarNode('facility')->end() // syslog
+                            ->scalarNode('max_files')->end() // rotating
+                            ->scalarNode('action_level')->end() // fingerscrossed
+                            ->scalarNode('buffer_size')->end() // fingerscrossed and buffer
+                            ->scalarNode('handler')->end() // fingerscrossed and buffer
                             ->scalarNode('formatter')->end()
                         ->end()
                         ->append($this->getProcessorsNode())
                         ->validate()
-                            ->ifTrue(function($v) { return 'fingerscrossed' === $v['type'] && !isset($v['handler']); })
+                            ->ifTrue(function($v) { return ('fingerscrossed' === $v['type'] || 'buffer' === $v['type']) && 1 !== count($v['handler']); })
                             ->thenInvalid('The handler has to be specified to use a FingersCrossedHandler')
                         ->end()
                         ->validate()

+ 34 - 3
src/Symfony/Bundle/MonologBundle/DependencyInjection/MonologExtension.php

@@ -55,13 +55,19 @@ class MonologExtension extends Extension
 
             $handlers = array();
             foreach ($config['handlers'] as $name => $handler) {
-                $handlers[] = $this->buildHandler($container, $name, $handler);
+                $handlers[] = array('id' => $this->buildHandler($container, $name, $handler), 'priority' => $handler['priority'] );
             }
 
             $handlers = array_reverse($handlers);
+            uasort($handlers, function($a, $b) {
+                if ($a['priority'] == $b['priority']) {
+                    return 0;
+                }
+                return $a['priority'] < $b['priority'] ? -1 : 1;
+            });
             foreach ($handlers as $handler) {
-                if (!in_array($handler, $this->nestedHandlers)) {
-                    $logger->addMethodCall('pushHandler', array(new Reference($handler)));
+                if (!in_array($handler['id'], $this->nestedHandlers)) {
+                    $logger->addMethodCall('pushHandler', array(new Reference($handler['id'])));
                 }
             }
         }
@@ -118,6 +124,19 @@ class MonologExtension extends Extension
             ));
             break;
 
+        case 'rotatingfile':
+            if (!isset($handler['path'])) {
+                $handler['path'] = '%kernel.logs_dir%/%kernel.environment%.log';
+            }
+
+            $definition->setArguments(array(
+                $handler['path'],
+                isset($handler['max_files']) ? $handler['max_files'] : 0,
+                $handler['level'],
+                $handler['bubble'],
+            ));
+            break;
+
         case 'fingerscrossed':
             if (!isset($handler['action_level'])) {
                 $handler['action_level'] = 'WARNING';
@@ -134,6 +153,18 @@ class MonologExtension extends Extension
             ));
             break;
 
+        case 'buffer':
+            $nestedHandlerId = $this->getHandlerId($handler['handler']);
+            array_push($this->nestedHandlers, $nestedHandlerId);
+
+            $definition->setArguments(array(
+                new Reference($nestedHandlerId),
+                isset($handler['buffer_size']) ? $handler['buffer_size'] : 0,
+                $handler['level'],
+                $handler['bubble'],
+            ));
+            break;
+
         case 'syslog':
             if (!isset($handler['ident'])) {
                 $handler['ident'] = false;

+ 2 - 0
src/Symfony/Bundle/MonologBundle/Resources/config/monolog.xml

@@ -8,6 +8,8 @@
         <parameter key="monolog.logger.class">Symfony\Bundle\MonologBundle\Logger\Logger</parameter>
         <parameter key="monolog.handler.stream.class">Monolog\Handler\StreamHandler</parameter>
         <parameter key="monolog.handler.fingerscrossed.class">Monolog\Handler\FingersCrossedHandler</parameter>
+        <parameter key="monolog.handler.buffer.class">Monolog\Handler\BufferHandler</parameter>
+        <parameter key="monolog.handler.rotatingfile.class">Monolog\Handler\RotatingFileHandler</parameter>
         <parameter key="monolog.handler.syslog.class">Monolog\Handler\SyslogHandler</parameter>
         <parameter key="monolog.handler.null.class">Monolog\Handler\NullHandler</parameter>
         <parameter key="monolog.handler.test.class">Monolog\Handler\TestHandler</parameter>

+ 2 - 0
src/Symfony/Bundle/MonologBundle/Resources/config/schema/monolog-1.0.xsd

@@ -19,6 +19,7 @@
             <xsd:element name="processor" type="processor" />
         </xsd:choice>
         <xsd:attribute name="type" type="xsd:string" use="required" />
+        <xsd:attribute name="priority" type="xsd:integer" />
         <xsd:attribute name="level" type="level" />
         <xsd:attribute name="bubble" type="xsd:boolean" />
         <xsd:attribute name="path" type="xsd:string" />
@@ -28,6 +29,7 @@
         <xsd:attribute name="facility" type="xsd:string" />
         <xsd:attribute name="action-level" type="level" />
         <xsd:attribute name="buffer-size" type="xsd:integer" />
+        <xsd:attribute name="max-files" type="xsd:integer" />
         <xsd:attribute name="handler" type="xsd:string" />
         <xsd:attribute name="formatter" type="xsd:string" />
     </xsd:complexType>

+ 141 - 3
src/Symfony/Bundle/MonologBundle/Tests/DependencyInjection/MonologExtensionTest.php

@@ -20,7 +20,6 @@ class MonologExtensionTest extends TestCase
 {
     public function testLoadWithDefault()
     {
-        // logger
         $container = new ContainerBuilder();
         $loader = new MonologExtension();
 
@@ -38,7 +37,6 @@ class MonologExtensionTest extends TestCase
 
     public function testLoadWithCustomValues()
     {
-        // logger
         $container = new ContainerBuilder();
         $loader = new MonologExtension();
 
@@ -56,7 +54,6 @@ class MonologExtensionTest extends TestCase
 
     public function testLoadWithSeveralHandlers()
     {
-        // logger
         $container = new ContainerBuilder();
         $loader = new MonologExtension();
 
@@ -83,6 +80,147 @@ class MonologExtensionTest extends TestCase
         $this->assertDICConstructorArguments($handler, array(new Reference('monolog.handler.nested'), \Monolog\Logger::ERROR, 0, false));
     }
 
+    public function testLoadWithOverwriting()
+    {
+        $container = new ContainerBuilder();
+        $loader = new MonologExtension();
+
+        $loader->load(array(
+            array('handlers' => array(
+                'custom' => array('type' => 'stream', 'path' => '/tmp/symfony.log', 'bubble' => true, 'level' => 'ERROR'),
+                'main' => array('type' => 'fingerscrossed', 'action_level' => 'ERROR', 'handler' => 'nested'),
+                'nested' => array('type' => 'stream')
+            )),
+            array('handlers' => array(
+                'custom' => array('type' => 'stream', 'path' => '/tmp/symfony.log', 'bubble' => true, 'level' => 'WARNING'),
+            ))
+        ), $container);
+        $this->assertTrue($container->hasDefinition('monolog.logger'));
+        $this->assertTrue($container->hasDefinition('monolog.handler.custom'));
+        $this->assertTrue($container->hasDefinition('monolog.handler.main'));
+        $this->assertTrue($container->hasDefinition('monolog.handler.nested'));
+
+        $logger = $container->getDefinition('monolog.logger');
+        $this->assertDICDefinitionMethodCallAt(1, $logger, 'pushHandler', array(new Reference('monolog.handler.custom')));
+        $this->assertDICDefinitionMethodCallAt(0, $logger, 'pushHandler', array(new Reference('monolog.handler.main')));
+
+        $handler = $container->getDefinition('monolog.handler.custom');
+        $this->assertDICDefinitionClass($handler, '%monolog.handler.stream.class%');
+        $this->assertDICConstructorArguments($handler, array('/tmp/symfony.log', \Monolog\Logger::WARNING, true));
+
+        $handler = $container->getDefinition('monolog.handler.main');
+        $this->assertDICDefinitionClass($handler, '%monolog.handler.fingerscrossed.class%');
+        $this->assertDICConstructorArguments($handler, array(new Reference('monolog.handler.nested'), \Monolog\Logger::ERROR, 0, false));
+    }
+
+    public function testLoadWithNewAtEnd()
+    {
+        $container = new ContainerBuilder();
+        $loader = new MonologExtension();
+
+        $loader->load(array(
+            array('handlers' => array(
+                'custom' => array('type' => 'stream', 'path' => '/tmp/symfony.log', 'bubble' => true, 'level' => 'ERROR'),
+                'main' => array('type' => 'fingerscrossed', 'action_level' => 'ERROR', 'handler' => 'nested'),
+                'nested' => array('type' => 'stream')
+            )),
+            array('handlers' => array(
+                'custom' => array('type' => 'stream', 'path' => '/tmp/symfony.log', 'bubble' => true, 'level' => 'WARNING'),
+                'new' => array('type' => 'stream', 'path' => '/tmp/monolog.log', 'bubble' => true, 'level' => 'ERROR'),
+            ))
+        ), $container);
+        $this->assertTrue($container->hasDefinition('monolog.logger'));
+        $this->assertTrue($container->hasDefinition('monolog.handler.custom'));
+        $this->assertTrue($container->hasDefinition('monolog.handler.main'));
+        $this->assertTrue($container->hasDefinition('monolog.handler.nested'));
+        $this->assertTrue($container->hasDefinition('monolog.handler.new'));
+
+        $logger = $container->getDefinition('monolog.logger');
+        $this->assertDICDefinitionMethodCallAt(2, $logger, 'pushHandler', array(new Reference('monolog.handler.new')));
+        $this->assertDICDefinitionMethodCallAt(1, $logger, 'pushHandler', array(new Reference('monolog.handler.custom')));
+        $this->assertDICDefinitionMethodCallAt(0, $logger, 'pushHandler', array(new Reference('monolog.handler.main')));
+
+        $handler = $container->getDefinition('monolog.handler.new');
+        $this->assertDICDefinitionClass($handler, '%monolog.handler.stream.class%');
+        $this->assertDICConstructorArguments($handler, array('/tmp/monolog.log', \Monolog\Logger::ERROR, true));
+    }
+
+    public function testLoadWithNewAndPriority()
+    {
+        $container = new ContainerBuilder();
+        $loader = new MonologExtension();
+
+        $loader->load(array(
+            array('handlers' => array(
+                'custom' => array('type' => 'stream', 'path' => '/tmp/symfony.log', 'bubble' => true, 'level' => 'ERROR'),
+                'main' => array('type' => 'buffer', 'level' => 'INFO', 'handler' => 'nested'),
+                'nested' => array('type' => 'stream')
+            )),
+            array('handlers' => array(
+                'custom' => array('type' => 'stream', 'path' => '/tmp/symfony.log', 'bubble' => true, 'level' => 'WARNING'),
+                'first' => array('type' => 'rotatingfile', 'path' => '/tmp/monolog.log', 'bubble' => true, 'level' => 'ERROR', 'priority' => 3),
+                'last' => array('type' => 'stream', 'path' => '/tmp/last.log', 'bubble' => true, 'level' => 'ERROR', 'priority' => -3),
+            ))
+        ), $container);
+        $this->assertTrue($container->hasDefinition('monolog.logger'));
+        $this->assertTrue($container->hasDefinition('monolog.handler.custom'));
+        $this->assertTrue($container->hasDefinition('monolog.handler.main'));
+        $this->assertTrue($container->hasDefinition('monolog.handler.nested'));
+        $this->assertTrue($container->hasDefinition('monolog.handler.first'));
+        $this->assertTrue($container->hasDefinition('monolog.handler.last'));
+
+        $logger = $container->getDefinition('monolog.logger');
+        $this->assertDICDefinitionMethodCallAt(2, $logger, 'pushHandler', array(new Reference('monolog.handler.last')));
+        $this->assertDICDefinitionMethodCallAt(1, $logger, 'pushHandler', array(new Reference('monolog.handler.custom')));
+        $this->assertDICDefinitionMethodCallAt(0, $logger, 'pushHandler', array(new Reference('monolog.handler.main')));
+        $this->assertDICDefinitionMethodCallAt(2, $logger, 'pushHandler', array(new Reference('monolog.handler.first')));
+
+        $handler = $container->getDefinition('monolog.handler.main');
+        $this->assertDICDefinitionClass($handler, '%monolog.handler.buffer.class%');
+        $this->assertDICConstructorArguments($handler, array(new Reference('monolog.handler.nested'), 0, \Monolog\Logger::INFO, false));
+
+        $handler = $container->getDefinition('monolog.handler.first');
+        $this->assertDICDefinitionClass($handler, '%monolog.handler.rotatingfile.class%');
+        $this->assertDICConstructorArguments($handler, array('/tmp/monolog.log', 0, \Monolog\Logger::ERROR, true));
+
+        $handler = $container->getDefinition('monolog.handler.last');
+        $this->assertDICDefinitionClass($handler, '%monolog.handler.stream.class%');
+        $this->assertDICConstructorArguments($handler, array('/tmp/last.log', \Monolog\Logger::ERROR, true));
+    }
+
+    /**
+     * @expectedException Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
+     */
+    public function testExceptionWhenUsingFingerscrossedWithoutHandler()
+    {
+        $container = new ContainerBuilder();
+        $loader = new MonologExtension();
+
+        $loader->load(array(array('handlers' => array('main' => array('type' => 'fingerscrossed')))), $container);
+    }
+
+    /**
+     * @expectedException Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
+     */
+    public function testExceptionWhenUsingBufferWithoutHandler()
+    {
+        $container = new ContainerBuilder();
+        $loader = new MonologExtension();
+
+        $loader->load(array(array('handlers' => array('main' => array('type' => 'buffer')))), $container);
+    }
+
+    /**
+     * @expectedException Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
+     */
+    public function testExceptionWhenUsingServiceWithoutId()
+    {
+        $container = new ContainerBuilder();
+        $loader = new MonologExtension();
+
+        $loader->load(array(array('handlers' => array('main' => array('type' => 'service')))), $container);
+    }
+
     /**
      * @expectedException Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
      */