Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature #24300 [HttpKernel][FrameworkBundle] Add a minimalist default…
… PSR-3 logger (dunglas) This PR was squashed before being merged into the 3.4 branch (closes #24300). Discussion ---------- [HttpKernel][FrameworkBundle] Add a minimalist default PSR-3 logger | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes <!-- don't forget updating src/**/CHANGELOG.md files --> | BC breaks? | no | Deprecations? | no <!-- don't forget updating UPGRADE-*.md files --> | Tests pass? | yes | Fixed tickets | n/a | License | MIT | Doc PR | n/a This PR provides a minimalist PSR-3 logger that is always available when FrameworkBundle is installed. By default, it writes errors on `stderr`, regular logs on `stdout` and discards debug data (this is configurable). This approach has several benefits: - It's what expect from an app logging systems of major containerization and orchestration tools including [Docker](https://docs.docker.com/engine/admin/logging/view_container_logs/) and [Kubernetes](https://kubernetes.io/docs/concepts/cluster-administration/logging/), as well as most cloud providers such as [Heroku](https://devcenter.heroku.com/articles/logging#writing-to-your-log) and [Google Container Engine](https://kubernetes.io/docs/tasks/debug-application-cluster/logging-stackdriver/). If the app follows this standard (and it's not currently the case with Symfony by default) logs will be automatically collected, aggregated and stored. - It's in sync with the "back to Unix roots" philosophy of Flex - Logs are directly displayed in the console when running the integrated PHP web server (`bin/console server:start` or Flex's `make serve`), Create React App also do that for instance. - It fixes a common problem when installing Flex recipes: many bundles expect a logger service but currently there is none available by default, and you usually get a `"logger" service not found error` (because packages depend of the PSR, but the PSR doesn't provide a logger service). Commits ------- 9a06513 [HttpKernel][FrameworkBundle] Add a minimalist default PSR-3 logger
- Loading branch information
Showing
10 changed files
with
433 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
src/Symfony/Component/HttpKernel/DependencyInjection/LoggerPass.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\HttpKernel\DependencyInjection; | ||
|
||
use Psr\Log\LoggerInterface; | ||
use Psr\Log\LogLevel; | ||
use Symfony\Component\HttpKernel\Log\Logger; | ||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
|
||
/** | ||
* Registers the default logger if necessary. | ||
* | ||
* @author Kévin Dunglas <dunglas@gmail.com> | ||
*/ | ||
class LoggerPass implements CompilerPassInterface | ||
{ | ||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function process(ContainerBuilder $container) | ||
{ | ||
$alias = $container->setAlias(LoggerInterface::class, 'logger'); | ||
$alias->setPublic(false); | ||
|
||
if ($container->has('logger')) { | ||
return; | ||
} | ||
|
||
$loggerDefinition = $container->register('logger', Logger::class); | ||
$loggerDefinition->setPublic(false); | ||
if ($container->getParameter('kernel.debug')) { | ||
$loggerDefinition->addArgument(LogLevel::DEBUG); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\HttpKernel\Log; | ||
|
||
use Psr\Log\AbstractLogger; | ||
use Psr\Log\InvalidArgumentException; | ||
use Psr\Log\LogLevel; | ||
|
||
/** | ||
* Minimalist PSR-3 logger designed to write in stderr or any other stream. | ||
* | ||
* @author Kévin Dunglas <dunglas@gmail.com> | ||
*/ | ||
class Logger extends AbstractLogger | ||
{ | ||
private static $levels = array( | ||
LogLevel::DEBUG => 0, | ||
LogLevel::INFO => 1, | ||
LogLevel::NOTICE => 2, | ||
LogLevel::WARNING => 3, | ||
LogLevel::ERROR => 4, | ||
LogLevel::CRITICAL => 5, | ||
LogLevel::ALERT => 6, | ||
LogLevel::EMERGENCY => 7, | ||
); | ||
|
||
private $minLevelIndex; | ||
private $formatter; | ||
private $handle; | ||
|
||
public function __construct($minLevel = LogLevel::WARNING, $output = 'php://stderr', callable $formatter = null) | ||
{ | ||
if (!isset(self::$levels[$minLevel])) { | ||
throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $minLevel)); | ||
} | ||
|
||
$this->minLevelIndex = self::$levels[$minLevel]; | ||
$this->formatter = $formatter ?: array($this, 'format'); | ||
if (false === $this->handle = @fopen($output, 'a')) { | ||
throw new InvalidArgumentException(sprintf('Unable to open "%s".', $output)); | ||
} | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function log($level, $message, array $context = array()) | ||
{ | ||
if (!isset(self::$levels[$level])) { | ||
throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); | ||
} | ||
|
||
if (self::$levels[$level] < $this->minLevelIndex) { | ||
return; | ||
} | ||
|
||
$formatter = $this->formatter; | ||
fwrite($this->handle, $formatter($level, $message, $context)); | ||
} | ||
|
||
/** | ||
* @param string $level | ||
* @param string $message | ||
* @param array $context | ||
* | ||
* @return string | ||
*/ | ||
private function format($level, $message, array $context) | ||
{ | ||
if (false !== strpos($message, '{')) { | ||
$replacements = array(); | ||
foreach ($context as $key => $val) { | ||
if (null === $val || is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) { | ||
$replacements["{{$key}}"] = $val; | ||
} elseif ($val instanceof \DateTimeInterface) { | ||
$replacements["{{$key}}"] = $val->format(\DateTime::RFC3339); | ||
} elseif (\is_object($val)) { | ||
$replacements["{{$key}}"] = '[object '.\get_class($val).']'; | ||
} else { | ||
$replacements["{{$key}}"] = '['.\gettype($val).']'; | ||
} | ||
} | ||
|
||
$message = strtr($message, $replacements); | ||
} | ||
|
||
return sprintf('%s [%s] %s', date(\DateTime::RFC3339), $level, $message).\PHP_EOL; | ||
} | ||
} |
68 changes: 68 additions & 0 deletions
68
src/Symfony/Component/HttpKernel/Tests/DependencyInjection/LoggerPassTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Psr\Log\LoggerInterface; | ||
use Psr\Log\LogLevel; | ||
use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass; | ||
use Symfony\Component\HttpKernel\Log\Logger; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
|
||
/** | ||
* @author Kévin Dunglas <dunglas@gmail.com> | ||
*/ | ||
class LoggerPassTest extends TestCase | ||
{ | ||
public function testAlwaysSetAutowiringAlias() | ||
{ | ||
$container = new ContainerBuilder(); | ||
$container->register('logger', 'Foo'); | ||
|
||
(new LoggerPass())->process($container); | ||
|
||
$this->assertFalse($container->getAlias(LoggerInterface::class)->isPublic()); | ||
} | ||
|
||
public function testDoNotOverrideExistingLogger() | ||
{ | ||
$container = new ContainerBuilder(); | ||
$container->register('logger', 'Foo'); | ||
|
||
(new LoggerPass())->process($container); | ||
|
||
$this->assertSame('Foo', $container->getDefinition('logger')->getClass()); | ||
} | ||
|
||
public function testRegisterLogger() | ||
{ | ||
$container = new ContainerBuilder(); | ||
$container->setParameter('kernel.debug', false); | ||
|
||
(new LoggerPass())->process($container); | ||
|
||
$definition = $container->getDefinition('logger'); | ||
$this->assertSame(Logger::class, $definition->getClass()); | ||
$this->assertFalse($definition->isPublic()); | ||
} | ||
|
||
public function testSetMinLevelWhenDebugging() | ||
{ | ||
$container = new ContainerBuilder(); | ||
$container->setParameter('kernel.debug', true); | ||
|
||
(new LoggerPass())->process($container); | ||
|
||
$definition = $container->getDefinition('logger'); | ||
$this->assertSame(LogLevel::DEBUG, $definition->getArgument(0)); | ||
} | ||
} |
Oops, something went wrong.