Skip to content

Commit 9a06513

Browse files
dunglasfabpot
authored andcommitted
[HttpKernel][FrameworkBundle] Add a minimalist default PSR-3 logger
1 parent 648a895 commit 9a06513

File tree

10 files changed

+433
-0
lines changed

10 files changed

+433
-0
lines changed

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
"provide": {
110110
"psr/cache-implementation": "1.0",
111111
"psr/container-implementation": "1.0",
112+
"psr/log-implementation": "1.0",
112113
"psr/simple-cache-implementation": "1.0"
113114
},
114115
"autoload": {

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
3.4.0
55
-----
66

7+
* Always register a minimalist logger that writes in `stderr`
78
* Deprecated `profiler.matcher` option
89
* Added support for `EventSubscriberInterface` on `MicroKernelTrait`
910
* Removed `doctrine/cache` from the list of required dependencies in `composer.json`

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use Symfony\Component\HttpKernel\DependencyInjection\AddCacheClearerPass;
3232
use Symfony\Component\HttpKernel\DependencyInjection\AddCacheWarmerPass;
3333
use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass;
34+
use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass;
3435
use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass;
3536
use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass;
3637
use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass;
@@ -85,6 +86,7 @@ public function build(ContainerBuilder $container)
8586
{
8687
parent::build($container);
8788

89+
$container->addCompilerPass(new LoggerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
8890
$container->addCompilerPass(new RegisterControllerArgumentLocatorsPass());
8991
$container->addCompilerPass(new RemoveEmptyControllerArgumentLocatorsPass(), PassConfig::TYPE_BEFORE_REMOVING);
9092
$container->addCompilerPass(new RoutingResolverPass());

src/Symfony/Component/Console/Logger/ConsoleLogger.php

+2
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ public function log($level, $message, array $context = array())
101101

102102
/**
103103
* Returns true when any messages have been logged at error levels.
104+
*
105+
* @return bool
104106
*/
105107
public function hasErrored()
106108
{

src/Symfony/Component/HttpKernel/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
3.4.0
55
-----
66

7+
* added a minimalist PSR-3 `Logger` class that writes in `stderr`
78
* made kernels implementing `CompilerPassInterface` able to process the container
89
* deprecated bundle inheritance
910
* added `RebootableInterface` and implemented it in `Kernel`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Component\HttpKernel\DependencyInjection;
13+
14+
use Psr\Log\LoggerInterface;
15+
use Psr\Log\LogLevel;
16+
use Symfony\Component\HttpKernel\Log\Logger;
17+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
18+
use Symfony\Component\DependencyInjection\ContainerBuilder;
19+
20+
/**
21+
* Registers the default logger if necessary.
22+
*
23+
* @author Kévin Dunglas <dunglas@gmail.com>
24+
*/
25+
class LoggerPass implements CompilerPassInterface
26+
{
27+
/**
28+
* {@inheritdoc}
29+
*/
30+
public function process(ContainerBuilder $container)
31+
{
32+
$alias = $container->setAlias(LoggerInterface::class, 'logger');
33+
$alias->setPublic(false);
34+
35+
if ($container->has('logger')) {
36+
return;
37+
}
38+
39+
$loggerDefinition = $container->register('logger', Logger::class);
40+
$loggerDefinition->setPublic(false);
41+
if ($container->getParameter('kernel.debug')) {
42+
$loggerDefinition->addArgument(LogLevel::DEBUG);
43+
}
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Component\HttpKernel\Log;
13+
14+
use Psr\Log\AbstractLogger;
15+
use Psr\Log\InvalidArgumentException;
16+
use Psr\Log\LogLevel;
17+
18+
/**
19+
* Minimalist PSR-3 logger designed to write in stderr or any other stream.
20+
*
21+
* @author Kévin Dunglas <dunglas@gmail.com>
22+
*/
23+
class Logger extends AbstractLogger
24+
{
25+
private static $levels = array(
26+
LogLevel::DEBUG => 0,
27+
LogLevel::INFO => 1,
28+
LogLevel::NOTICE => 2,
29+
LogLevel::WARNING => 3,
30+
LogLevel::ERROR => 4,
31+
LogLevel::CRITICAL => 5,
32+
LogLevel::ALERT => 6,
33+
LogLevel::EMERGENCY => 7,
34+
);
35+
36+
private $minLevelIndex;
37+
private $formatter;
38+
private $handle;
39+
40+
public function __construct($minLevel = LogLevel::WARNING, $output = 'php://stderr', callable $formatter = null)
41+
{
42+
if (!isset(self::$levels[$minLevel])) {
43+
throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $minLevel));
44+
}
45+
46+
$this->minLevelIndex = self::$levels[$minLevel];
47+
$this->formatter = $formatter ?: array($this, 'format');
48+
if (false === $this->handle = @fopen($output, 'a')) {
49+
throw new InvalidArgumentException(sprintf('Unable to open "%s".', $output));
50+
}
51+
}
52+
53+
/**
54+
* {@inheritdoc}
55+
*/
56+
public function log($level, $message, array $context = array())
57+
{
58+
if (!isset(self::$levels[$level])) {
59+
throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
60+
}
61+
62+
if (self::$levels[$level] < $this->minLevelIndex) {
63+
return;
64+
}
65+
66+
$formatter = $this->formatter;
67+
fwrite($this->handle, $formatter($level, $message, $context));
68+
}
69+
70+
/**
71+
* @param string $level
72+
* @param string $message
73+
* @param array $context
74+
*
75+
* @return string
76+
*/
77+
private function format($level, $message, array $context)
78+
{
79+
if (false !== strpos($message, '{')) {
80+
$replacements = array();
81+
foreach ($context as $key => $val) {
82+
if (null === $val || is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) {
83+
$replacements["{{$key}}"] = $val;
84+
} elseif ($val instanceof \DateTimeInterface) {
85+
$replacements["{{$key}}"] = $val->format(\DateTime::RFC3339);
86+
} elseif (\is_object($val)) {
87+
$replacements["{{$key}}"] = '[object '.\get_class($val).']';
88+
} else {
89+
$replacements["{{$key}}"] = '['.\gettype($val).']';
90+
}
91+
}
92+
93+
$message = strtr($message, $replacements);
94+
}
95+
96+
return sprintf('%s [%s] %s', date(\DateTime::RFC3339), $level, $message).\PHP_EOL;
97+
}
98+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Component\HttpKernel\Tests\DependencyInjection;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Psr\Log\LoggerInterface;
16+
use Psr\Log\LogLevel;
17+
use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass;
18+
use Symfony\Component\HttpKernel\Log\Logger;
19+
use Symfony\Component\DependencyInjection\ContainerBuilder;
20+
21+
/**
22+
* @author Kévin Dunglas <dunglas@gmail.com>
23+
*/
24+
class LoggerPassTest extends TestCase
25+
{
26+
public function testAlwaysSetAutowiringAlias()
27+
{
28+
$container = new ContainerBuilder();
29+
$container->register('logger', 'Foo');
30+
31+
(new LoggerPass())->process($container);
32+
33+
$this->assertFalse($container->getAlias(LoggerInterface::class)->isPublic());
34+
}
35+
36+
public function testDoNotOverrideExistingLogger()
37+
{
38+
$container = new ContainerBuilder();
39+
$container->register('logger', 'Foo');
40+
41+
(new LoggerPass())->process($container);
42+
43+
$this->assertSame('Foo', $container->getDefinition('logger')->getClass());
44+
}
45+
46+
public function testRegisterLogger()
47+
{
48+
$container = new ContainerBuilder();
49+
$container->setParameter('kernel.debug', false);
50+
51+
(new LoggerPass())->process($container);
52+
53+
$definition = $container->getDefinition('logger');
54+
$this->assertSame(Logger::class, $definition->getClass());
55+
$this->assertFalse($definition->isPublic());
56+
}
57+
58+
public function testSetMinLevelWhenDebugging()
59+
{
60+
$container = new ContainerBuilder();
61+
$container->setParameter('kernel.debug', true);
62+
63+
(new LoggerPass())->process($container);
64+
65+
$definition = $container->getDefinition('logger');
66+
$this->assertSame(LogLevel::DEBUG, $definition->getArgument(0));
67+
}
68+
}

0 commit comments

Comments
 (0)