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(