diff --git a/.travis.yml b/.travis.yml index 0594999c..00141522 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,9 +24,10 @@ matrix: env: SYMFONY_VERSION="2.8.*" - php: 5.6 env: SYMFONY_VERSION="3.4.*" DEPENDENCIES=dev - - php: 7.1 - php: 7.0 - php: 7.1 + # There is a bug in PHPUnit 5.7 + env: SYMFONY_PHPUNIT_VERSION=6.5 - php: 7.2 - php: 7.3 # Test against dev versions diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d19447f..f73ca5d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,13 @@ -## 3.3.2 (2018-12-29) +## 3.4.0 (xxxx-xx-xx) +* Deprecate "excluded_404s" option +* Flush loggers on `kernel.reset` +* Register processors (`ProcessorInterface`) for autoconfiguration (tag: `monolog.processor`) +* Expose configuration for the `ConsoleHandler` * Fixed psr-3 processing being applied to all handlers, only leaf ones are now processing * Fixed regression when `app` channel is defined explicitly * Fixed handlers marked as nested not being ignored properly from the stack +* Added configuration support for Redis ## 3.3.1 (2018-11-04) @@ -14,6 +19,7 @@ * Added timeouts to the pushover, hipchat, slack handlers * Dropped support for PHP 5.3, 5.4, and HHVM * Added configuration for HttpCodeActivationStrategy +* Deprecated "excluded_404s" option for Symfony >= 3.4 ## 3.2.0 (2018-03-05) diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 20356b50..4eea7fe1 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -31,11 +31,14 @@ * - path: string * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true + * - [file_permission]: int|null, defaults to null (0644) + * - [use_locking]: bool, defaults to false * * - console: * - [verbosity_levels]: level => verbosity configuration * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true + * - [console_formater_options]: array * * - firephp: * - [level]: level name or int value, defaults to DEBUG @@ -85,6 +88,21 @@ * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * + * - redis: + * - redis: + * - id: optional if host is given + * - host: 127.0.0.1 + * - password: null + * - port: 6379 + * - database: 0 + * - key_name: monolog_redis + * + * - predis: + * - redis: + * - id: optional if host is given + * - host: tcp://10.0.0.1:6379 + * - key_name: monolog_redis + * * - fingers_crossed: * - handler: the wrapped handler's name * - [action_level|activation_strategy]: minimum level or service id to activate the handler, defaults to WARNING @@ -110,7 +128,7 @@ * - [flush_on_overflow]: bool, defaults to false * * - deduplication: - * - handler: the wrapper handler's name + * - handler: the wrapped handler's name * - [store]: The file/path where the deduplication log should be kept, defaults to %kernel.cache_dir%/monolog_dedup_* * - [deduplication_level]: The minimum logging level for log records to be looked at for deduplication purposes, defaults to ERROR * - [time]: The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through, defaults to 60 @@ -138,6 +156,7 @@ * - [logopts]: defaults to LOG_PID * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true + * - [ident]: string, defaults to * * - swift_mailer: * - from_email: optional if email_prototype is given @@ -359,9 +378,10 @@ public function getConfigTreeBuilder() }) ->end() ->end() + ->booleanNode('use_locking')->defaultFalse()->end() // stream ->scalarNode('filename_format')->defaultValue('{filename}-{date}')->end() //rotating ->scalarNode('date_format')->defaultValue('Y-m-d')->end() //rotating - ->scalarNode('ident')->defaultFalse()->end() // syslog + ->scalarNode('ident')->defaultFalse()->end() // syslog and syslogudp ->scalarNode('logopts')->defaultValue(LOG_PID)->end() // syslog ->scalarNode('facility')->defaultValue('user')->end() // syslog ->scalarNode('max_files')->defaultValue(0)->end() // rotating @@ -517,6 +537,44 @@ public function getConfigTreeBuilder() ->scalarNode('index')->defaultValue('monolog')->end() // elasticsearch ->scalarNode('document_type')->defaultValue('logs')->end() // elasticsearch ->scalarNode('ignore_error')->defaultValue(false)->end() // elasticsearch + ->arrayNode('redis') + ->canBeUnset() + ->beforeNormalization() + ->ifString() + ->then(function ($v) { return array('id' => $v); }) + ->end() + ->children() + ->scalarNode('id')->end() + ->scalarNode('host')->end() + ->scalarNode('password')->defaultNull()->end() + ->scalarNode('port')->defaultValue(6379)->end() + ->scalarNode('database')->defaultValue(0)->end() + ->scalarNode('key_name')->defaultValue('monolog_redis')->end() + ->end() + ->validate() + ->ifTrue(function ($v) { + return !isset($v['id']) && !isset($v['host']); + }) + ->thenInvalid('What must be set is either the host or the service id of the Redis client.') + ->end() + ->end() // redis + ->arrayNode('predis') + ->canBeUnset() + ->beforeNormalization() + ->ifString() + ->then(function ($v) { return array('id' => $v); }) + ->end() + ->children() + ->scalarNode('id')->end() + ->scalarNode('host')->end() + ->end() + ->validate() + ->ifTrue(function ($v) { + return !isset($v['id']) && !isset($v['host']); + }) + ->thenInvalid('What must be set is either the host or the service id of the Predis client.') + ->end() + ->end() // predis ->arrayNode('config') ->canBeUnset() ->prototype('scalar')->end() @@ -560,6 +618,7 @@ public function getConfigTreeBuilder() ->scalarNode('client_id')->defaultNull()->end() // raven_handler ->scalarNode('auto_log_stacks')->defaultFalse()->end() // raven_handler ->scalarNode('release')->defaultNull()->end() // raven_handler + ->scalarNode('environment')->defaultNull()->end() // raven_handler ->scalarNode('message_type')->defaultValue(0)->end() // error_log ->arrayNode('tags') // loggly ->beforeNormalization() @@ -571,6 +630,16 @@ public function getConfigTreeBuilder() ->then(function ($v) { return array_filter(array_map('trim', $v)); }) ->end() ->prototype('scalar')->end() + ->end() + // console + ->variableNode('console_formater_options') + ->defaultValue([]) + ->validate() + ->ifTrue(function ($v) { + return !is_array($v); + }) + ->thenInvalid('console_formater_options must an array.') + ->end() ->end() ->arrayNode('verbosity_levels') // console ->beforeNormalization() @@ -833,6 +902,14 @@ public function getConfigTreeBuilder() ->ifTrue(function ($v) { return 'server_log' === $v['type'] && empty($v['host']); }) ->thenInvalid('The host has to be specified to use a ServerLogHandler') ->end() + ->validate() + ->ifTrue(function ($v) { return 'redis' === $v['type'] && empty($v['redis']); }) + ->thenInvalid('The host has to be specified to use a RedisLogHandler') + ->end() + ->validate() + ->ifTrue(function ($v) { return 'predis' === $v['type'] && empty($v['redis']); }) + ->thenInvalid('The host has to be specified to use a RedisLogHandler') + ->end() ->end() ->validate() ->ifTrue(function ($v) { return isset($v['debug']); }) diff --git a/DependencyInjection/MonologExtension.php b/DependencyInjection/MonologExtension.php index bd3da937..02bfbecd 100644 --- a/DependencyInjection/MonologExtension.php +++ b/DependencyInjection/MonologExtension.php @@ -11,6 +11,13 @@ namespace Symfony\Bundle\MonologBundle\DependencyInjection; +use Monolog\Processor\ProcessorInterface; +use Monolog\ResettableInterface; +use Predis; +use Redis; +use Symfony\Bridge\Monolog\Handler\FingersCrossed\HttpCodeActivationStrategy; +use Symfony\Bridge\Monolog\Processor\TokenProcessor; +use Symfony\Bridge\Monolog\Processor\WebProcessor; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -108,6 +115,18 @@ public function load(array $configs, ContainerBuilder $container) } $container->setParameter('monolog.additional_channels', isset($config['channels']) ? $config['channels'] : array()); + + if (method_exists($container, 'registerForAutoconfiguration')) { + if (interface_exists(ProcessorInterface::class)) { + $container->registerForAutoconfiguration(ProcessorInterface::class) + ->addTag('monolog.processor'); + } else { + $container->registerForAutoconfiguration(WebProcessor::class) + ->addTag('monolog.processor'); + } + $container->registerForAutoconfiguration(TokenProcessor::class) + ->addTag('monolog.processor'); + } } /** @@ -138,7 +157,8 @@ private function buildHandler(ContainerBuilder $container, $name, array $handler return $handlerId; } - $definition = new Definition($this->getHandlerClassByType($handler['type'])); + $handlerClass = $this->getHandlerClassByType($handler['type']); + $definition = new Definition($handlerClass); $handler['level'] = $this->levelToMonologConst($handler['level']); @@ -168,6 +188,7 @@ private function buildHandler(ContainerBuilder $container, $name, array $handler $handler['level'], $handler['bubble'], $handler['file_permission'], + $handler['use_locking'], )); break; @@ -176,6 +197,7 @@ private function buildHandler(ContainerBuilder $container, $name, array $handler null, $handler['bubble'], isset($handler['verbosity_levels']) ? $handler['verbosity_levels'] : array(), + $handler['console_formater_options'] )); $definition->addTag('kernel.event_subscriber'); break; @@ -291,6 +313,43 @@ private function buildHandler(ContainerBuilder $container, $name, array $handler $handler['bubble'], )); break; + case 'redis': + case 'predis': + if (isset($handler['redis']['id'])) { + $clientId = $handler['redis']['id']; + } elseif ('redis' === $handler['type']) { + if (!class_exists(Redis::class)) { + throw new \RuntimeException('The \Redis class is not available.'); + } + + $client = new Definition('\Redis'); + $client->addMethodCall('connect', array($handler['redis']['host'], $handler['redis']['port'])); + $client->addMethodCall('auth', array($handler['redis']['password'])); + $client->addMethodCall('select', array($handler['redis']['database'])); + $client->setPublic(false); + $clientId = uniqid('monolog.redis.client.', true); + $container->setDefinition($clientId, $client); + } else { + if (!class_exists(Predis\Client::class)) { + throw new \RuntimeException('The \Predis\Client class is not available.'); + } + + $client = new Definition('\Predis\Client'); + $client->setArguments(array( + $handler['redis']['host'], + )); + $client->setPublic(false); + + $clientId = uniqid('monolog.predis.client.', true); + $container->setDefinition($clientId, $client); + } + $definition->setArguments(array( + new Reference($clientId), + $handler['redis']['key_name'], + $handler['level'], + $handler['bubble'], + )); + break; case 'chromephp': $definition->setArguments(array( @@ -325,6 +384,9 @@ private function buildHandler(ContainerBuilder $container, $name, array $handler if (isset($handler['activation_strategy'])) { $activation = new Reference($handler['activation_strategy']); } elseif (!empty($handler['excluded_404s'])) { + if (class_exists(HttpCodeActivationStrategy::class)) { + @trigger_error('The "excluded_404s" option is deprecated in MonologBundle since version 3.4.0, you should rely on the "excluded_http_codes" option instead.', E_USER_DEPRECATED); + } $activationDef = new Definition('Symfony\Bridge\Monolog\Handler\FingersCrossed\NotFoundActivationStrategy', array( new Reference('request_stack'), $handler['excluded_404s'], @@ -436,6 +498,9 @@ private function buildHandler(ContainerBuilder $container, $name, array $handler $handler['level'], $handler['bubble'], )); + if ($handler['ident']) { + $definition->addArgument($handler['ident']); + } break; case 'swift_mailer': @@ -613,7 +678,10 @@ private function buildHandler(ContainerBuilder $container, $name, array $handler } else { $client = new Definition('Raven_Client', array( $handler['dsn'], - array('auto_log_stacks' => $handler['auto_log_stacks']) + array( + 'auto_log_stacks' => $handler['auto_log_stacks'], + 'environment' => $handler['environment'] + ) )); $client->setPublic(false); $clientId = 'monolog.raven.client.'.sha1($handler['dsn']); @@ -741,6 +809,11 @@ private function buildHandler(ContainerBuilder $container, $name, array $handler if (!empty($handler['formatter'])) { $definition->addMethodCall('setFormatter', array(new Reference($handler['formatter']))); } + + if (!in_array($handlerId, $this->nestedHandlers) && is_subclass_of($handlerClass, ResettableInterface::class)) { + $definition->addTag('kernel.reset', array('method' => 'reset')); + } + $container->setDefinition($handlerId, $definition); return $handlerId; @@ -801,6 +874,8 @@ private function getHandlerClassByType($handlerType) 'mongo' => 'Monolog\Handler\MongoDBHandler', 'elasticsearch' => 'Monolog\Handler\ElasticSearchHandler', 'server_log' => 'Symfony\Bridge\Monolog\Handler\ServerLogHandler', + 'redis' => 'Monolog\Handler\RedisHandler', + 'predis' => 'Monolog\Handler\RedisHandler', ); if (!isset($typeToClassMapping[$handlerType])) { diff --git a/Resources/config/schema/monolog-1.0.xsd b/Resources/config/schema/monolog-1.0.xsd index 8e7936fe..cf0eac87 100644 --- a/Resources/config/schema/monolog-1.0.xsd +++ b/Resources/config/schema/monolog-1.0.xsd @@ -34,6 +34,7 @@ + @@ -156,6 +157,20 @@ + + + + + + + + + + + + + + diff --git a/Tests/DependencyInjection/Compiler/LoggerChannelPassTest.php b/Tests/DependencyInjection/Compiler/LoggerChannelPassTest.php index 99e9426e..7743cbf7 100644 --- a/Tests/DependencyInjection/Compiler/LoggerChannelPassTest.php +++ b/Tests/DependencyInjection/Compiler/LoggerChannelPassTest.php @@ -140,6 +140,9 @@ public function testChannelsConfigurationOptionSupportsAppChannel() $container->setParameter('monolog.additional_channels', array('app')); $container->compile(); + + // the test ensures that the validation does not fail (i.e. it does not throw any exceptions) + $this->addToAssertionCount(1); } private function getContainer() diff --git a/Tests/DependencyInjection/ConfigurationTest.php b/Tests/DependencyInjection/ConfigurationTest.php index ffa9bf64..aa7a5b3c 100644 --- a/Tests/DependencyInjection/ConfigurationTest.php +++ b/Tests/DependencyInjection/ConfigurationTest.php @@ -324,6 +324,31 @@ public function testWithFilePermission() $this->assertSame(0777, $config['handlers']['bar']['file_permission']); } + public function testWithUseLocking() + { + $configs = array( + array( + 'handlers' => array( + 'foo' => array( + 'type' => 'stream', + 'path' => '/foo', + 'use_locking' => false, + ), + 'bar' => array( + 'type' => 'stream', + 'path' => '/bar', + 'use_locking' => true, + ) + ) + ) + ); + + $config = $this->process($configs); + + $this->assertFalse($config['handlers']['foo']['use_locking']); + $this->assertTrue($config['handlers']['bar']['use_locking']); + } + public function testWithNestedHandler() { $configs = array( @@ -337,6 +362,50 @@ public function testWithNestedHandler() $this->assertTrue($config['handlers']['foobar']['nested']); } + public function testWithRedisHandler() + { + $configs = array( + array( + 'handlers' => array( + 'redis' => array( + 'type' => 'redis', + 'redis' => array( + 'host' => '127.0.1.1', + 'password' => 'pa$$w0rd', + 'port' => 1234, + 'database' => 1, + 'key_name' => 'monolog_redis_test' + ) + ) + ) + ) + ); + $config = $this->process($configs); + + $this->assertEquals('127.0.1.1', $config['handlers']['redis']['redis']['host']); + $this->assertEquals('pa$$w0rd', $config['handlers']['redis']['redis']['password']); + $this->assertEquals(1234, $config['handlers']['redis']['redis']['port']); + $this->assertEquals(1, $config['handlers']['redis']['redis']['database']); + $this->assertEquals('monolog_redis_test', $config['handlers']['redis']['redis']['key_name']); + + $configs = array( + array( + 'handlers' => array( + 'redis' => array( + 'type' => 'predis', + 'redis' => array( + 'host' => '127.0.1.1', + 'key_name' => 'monolog_redis_test' + ) + ) + ) + ) + ); + $config = $this->process($configs); + + $this->assertEquals('127.0.1.1', $config['handlers']['redis']['redis']['host']); + $this->assertEquals('monolog_redis_test', $config['handlers']['redis']['redis']['key_name']); + } /** * Processes an array of configurations and returns a compiled version. diff --git a/Tests/DependencyInjection/FixtureMonologExtensionTest.php b/Tests/DependencyInjection/FixtureMonologExtensionTest.php index d5f9aa22..23c0cfd7 100644 --- a/Tests/DependencyInjection/FixtureMonologExtensionTest.php +++ b/Tests/DependencyInjection/FixtureMonologExtensionTest.php @@ -37,7 +37,7 @@ public function testLoadWithSeveralHandlers() $handler = $container->getDefinition('monolog.handler.custom'); $this->assertDICDefinitionClass($handler, 'Monolog\Handler\StreamHandler'); - $this->assertDICConstructorArguments($handler, array('/tmp/symfony.log', \Monolog\Logger::ERROR, false, 0666)); + $this->assertDICConstructorArguments($handler, array('/tmp/symfony.log', \Monolog\Logger::ERROR, false, 0666, false)); $handler = $container->getDefinition('monolog.handler.main'); $this->assertDICDefinitionClass($handler, 'Monolog\Handler\FingersCrossedHandler'); @@ -65,7 +65,7 @@ public function testLoadWithOverwriting() $handler = $container->getDefinition('monolog.handler.custom'); $this->assertDICDefinitionClass($handler, 'Monolog\Handler\StreamHandler'); - $this->assertDICConstructorArguments($handler, array('/tmp/symfony.log', \Monolog\Logger::WARNING, true, null)); + $this->assertDICConstructorArguments($handler, array('/tmp/symfony.log', \Monolog\Logger::WARNING, true, null, false)); $handler = $container->getDefinition('monolog.handler.main'); $this->assertDICDefinitionClass($handler, 'Monolog\Handler\FingersCrossedHandler'); @@ -91,7 +91,7 @@ public function testLoadWithNewAtEnd() $handler = $container->getDefinition('monolog.handler.new'); $this->assertDICDefinitionClass($handler, 'Monolog\Handler\StreamHandler'); - $this->assertDICConstructorArguments($handler, array('/tmp/monolog.log', \Monolog\Logger::ERROR, true, null)); + $this->assertDICConstructorArguments($handler, array('/tmp/monolog.log', \Monolog\Logger::ERROR, true, null, false)); } public function testLoadWithNewAndPriority() @@ -123,7 +123,7 @@ public function testLoadWithNewAndPriority() $handler = $container->getDefinition('monolog.handler.last'); $this->assertDICDefinitionClass($handler, 'Monolog\Handler\StreamHandler'); - $this->assertDICConstructorArguments($handler, array('/tmp/last.log', \Monolog\Logger::ERROR, true, null)); + $this->assertDICConstructorArguments($handler, array('/tmp/last.log', \Monolog\Logger::ERROR, true, null, false)); } public function testHandlersWithChannels() diff --git a/Tests/DependencyInjection/MonologExtensionTest.php b/Tests/DependencyInjection/MonologExtensionTest.php index 0310fe16..05c3aa6e 100644 --- a/Tests/DependencyInjection/MonologExtensionTest.php +++ b/Tests/DependencyInjection/MonologExtensionTest.php @@ -33,14 +33,14 @@ public function testLoadWithDefault() $handler = $container->getDefinition('monolog.handler.main'); $this->assertDICDefinitionClass($handler, 'Monolog\Handler\StreamHandler'); - $this->assertDICConstructorArguments($handler, array('%kernel.logs_dir%/%kernel.environment%.log', \Monolog\Logger::DEBUG, true, null)); + $this->assertDICConstructorArguments($handler, array('%kernel.logs_dir%/%kernel.environment%.log', \Monolog\Logger::DEBUG, true, null, false)); $this->assertDICDefinitionMethodCallAt(0, $handler, 'pushProcessor', array(new Reference('monolog.processor.psr_log_message'))); } public function testLoadWithCustomValues() { $container = $this->getContainer(array(array('handlers' => array( - 'custom' => array('type' => 'stream', 'path' => '/tmp/symfony.log', 'bubble' => false, 'level' => 'ERROR', 'file_permission' => '0666') + 'custom' => array('type' => 'stream', 'path' => '/tmp/symfony.log', 'bubble' => false, 'level' => 'ERROR', 'file_permission' => '0666', 'use_locking' => true) )))); $this->assertTrue($container->hasDefinition('monolog.logger')); $this->assertTrue($container->hasDefinition('monolog.handler.custom')); @@ -51,7 +51,7 @@ public function testLoadWithCustomValues() $handler = $container->getDefinition('monolog.handler.custom'); $this->assertDICDefinitionClass($handler, 'Monolog\Handler\StreamHandler'); - $this->assertDICConstructorArguments($handler, array('/tmp/symfony.log', \Monolog\Logger::ERROR, false, 0666)); + $this->assertDICConstructorArguments($handler, array('/tmp/symfony.log', \Monolog\Logger::ERROR, false, 0666, true)); } public function testLoadWithNestedHandler() @@ -71,7 +71,7 @@ public function testLoadWithNestedHandler() $handler = $container->getDefinition('monolog.handler.custom'); $this->assertDICDefinitionClass($handler, 'Monolog\Handler\StreamHandler'); - $this->assertDICConstructorArguments($handler, array('/tmp/symfony.log', \Monolog\Logger::ERROR, false, 0666)); + $this->assertDICConstructorArguments($handler, array('/tmp/symfony.log', \Monolog\Logger::ERROR, false, 0666, false)); } public function testLoadWithServiceHandler() @@ -383,6 +383,7 @@ public function testLogglyHandler() $this->assertDICDefinitionMethodCallAt(1, $handler, 'setTag', array('foo,bar')); } + /** @group legacy */ public function testFingersCrossedHandlerWhenExcluded404sAreSpecified() { $container = $this->getContainer(array(array('handlers' => array(