Skip to content

Commit 09afa64

Browse files
committed
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
2 parents 1b30098 + 9a06513 commit 09afa64

File tree

10 files changed

+433
-0
lines changed

10 files changed

+433
-0
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use Symfony\Component\Console\Application;
2929
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
3030
use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass;
31+
use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass;
3132
use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass;
3233
use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass;
3334
use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass;
@@ -82,6 +83,7 @@ public function build(ContainerBuilder $container)
8283
{
8384
parent::build($container);
8485

86+
$container->addCompilerPass(new LoggerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
8587
$container->addCompilerPass(new RegisterControllerArgumentLocatorsPass());
8688
$container->addCompilerPass(new RemoveEmptyControllerArgumentLocatorsPass(), PassConfig::TYPE_BEFORE_REMOVING);
8789
$container->addCompilerPass(new RoutingResolverPass());

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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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`
Lines changed: 45 additions & 0 deletions
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+
}
Lines changed: 98 additions & 0 deletions
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+
}
Lines changed: 68 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy