bug #34673 Migrate server:log command away from WebServerBundle (jderusse)
This PR was merged into the 4.4 branch.
Discussion
----------
Migrate server:log command away from WebServerBundle
| Q | A
| ------------- | ---
| Branch? | 4.4
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Tickets | #34657
| License | MIT
| Doc PR | NA
Duplicate ServerLogCommand in MonologBridge (currently in deprecated WebServerBundle) which does not have alternative in `symfony` bin.
Targeted 4.4 in order to provide a migration path to users.
Commits
-------
3bfa8dbb18
Migrate server:log command away from WebServerBundle
This commit is contained in:
commit
63dd7a310f
@ -6,6 +6,7 @@ CHANGELOG
|
|||||||
|
|
||||||
* The `RouteProcessor` class has been made final
|
* The `RouteProcessor` class has been made final
|
||||||
* Added `ElasticsearchLogstashHandler`
|
* Added `ElasticsearchLogstashHandler`
|
||||||
|
* Added the `ServerLogCommand`. Backport from the deprecated WebServerBundle
|
||||||
|
|
||||||
4.3.0
|
4.3.0
|
||||||
-----
|
-----
|
||||||
|
159
src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php
Normal file
159
src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
<?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\Bridge\Monolog\Command;
|
||||||
|
|
||||||
|
use Monolog\Formatter\FormatterInterface;
|
||||||
|
use Monolog\Logger;
|
||||||
|
use Symfony\Bridge\Monolog\Formatter\ConsoleFormatter;
|
||||||
|
use Symfony\Bridge\Monolog\Handler\ConsoleHandler;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Exception\LogicException;
|
||||||
|
use Symfony\Component\Console\Exception\RuntimeException;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||||
|
*/
|
||||||
|
class ServerLogCommand extends Command
|
||||||
|
{
|
||||||
|
private static $bgColor = ['black', 'blue', 'cyan', 'green', 'magenta', 'red', 'white', 'yellow'];
|
||||||
|
|
||||||
|
private $el;
|
||||||
|
private $handler;
|
||||||
|
|
||||||
|
protected static $defaultName = 'server:log';
|
||||||
|
|
||||||
|
public function isEnabled()
|
||||||
|
{
|
||||||
|
if (!class_exists(ConsoleFormatter::class)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// based on a symfony/symfony package, it crashes due a missing FormatterInterface from monolog/monolog
|
||||||
|
if (!interface_exists(FormatterInterface::class)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
if (!class_exists(ConsoleFormatter::class)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this
|
||||||
|
->addOption('host', null, InputOption::VALUE_REQUIRED, 'The server host', '0.0.0.0:9911')
|
||||||
|
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The line format', ConsoleFormatter::SIMPLE_FORMAT)
|
||||||
|
->addOption('date-format', null, InputOption::VALUE_REQUIRED, 'The date format', ConsoleFormatter::SIMPLE_DATE)
|
||||||
|
->addOption('filter', null, InputOption::VALUE_REQUIRED, 'An expression to filter log. Example: "level > 200 or channel in [\'app\', \'doctrine\']"')
|
||||||
|
->setDescription('Starts a log server that displays logs in real time')
|
||||||
|
->setHelp(<<<'EOF'
|
||||||
|
<info>%command.name%</info> starts a log server to display in real time the log
|
||||||
|
messages generated by your application:
|
||||||
|
|
||||||
|
<info>php %command.full_name%</info>
|
||||||
|
|
||||||
|
To get the information as a machine readable format, use the
|
||||||
|
<comment>--filter</> option:
|
||||||
|
|
||||||
|
<info>php %command.full_name% --filter=port</info>
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$filter = $input->getOption('filter');
|
||||||
|
if ($filter) {
|
||||||
|
if (!class_exists(ExpressionLanguage::class)) {
|
||||||
|
throw new LogicException('Package "symfony/expression-language" is required to use the "filter" option.');
|
||||||
|
}
|
||||||
|
$this->el = new ExpressionLanguage();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->handler = new ConsoleHandler($output, true, [
|
||||||
|
OutputInterface::VERBOSITY_NORMAL => Logger::DEBUG,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->handler->setFormatter(new ConsoleFormatter([
|
||||||
|
'format' => str_replace('\n', "\n", $input->getOption('format')),
|
||||||
|
'date_format' => $input->getOption('date-format'),
|
||||||
|
'colors' => $output->isDecorated(),
|
||||||
|
'multiline' => OutputInterface::VERBOSITY_DEBUG <= $output->getVerbosity(),
|
||||||
|
]));
|
||||||
|
|
||||||
|
if (false === strpos($host = $input->getOption('host'), '://')) {
|
||||||
|
$host = 'tcp://'.$host;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$socket = stream_socket_server($host, $errno, $errstr)) {
|
||||||
|
throw new RuntimeException(sprintf('Server start failed on "%s": %s %s.', $host, $errstr, $errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->getLogs($socket) as $clientId => $message) {
|
||||||
|
$record = unserialize(base64_decode($message));
|
||||||
|
|
||||||
|
// Impossible to decode the message, give up.
|
||||||
|
if (false === $record) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($filter && !$this->el->evaluate($filter, $record)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->displayLog($output, $clientId, $record);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getLogs($socket): iterable
|
||||||
|
{
|
||||||
|
$sockets = [(int) $socket => $socket];
|
||||||
|
$write = [];
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
$read = $sockets;
|
||||||
|
stream_select($read, $write, $write, null);
|
||||||
|
|
||||||
|
foreach ($read as $stream) {
|
||||||
|
if ($socket === $stream) {
|
||||||
|
$stream = stream_socket_accept($socket);
|
||||||
|
$sockets[(int) $stream] = $stream;
|
||||||
|
} elseif (feof($stream)) {
|
||||||
|
unset($sockets[(int) $stream]);
|
||||||
|
fclose($stream);
|
||||||
|
} else {
|
||||||
|
yield (int) $stream => fgets($stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function displayLog(OutputInterface $output, int $clientId, array $record)
|
||||||
|
{
|
||||||
|
if (isset($record['log_id'])) {
|
||||||
|
$clientId = unpack('H*', $record['log_id'])[1];
|
||||||
|
}
|
||||||
|
$logBlock = sprintf('<bg=%s> </>', self::$bgColor[$clientId % 8]);
|
||||||
|
$output->write($logBlock);
|
||||||
|
|
||||||
|
$this->handler->handle($record);
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Bundle\DebugBundle;
|
namespace Symfony\Bundle\DebugBundle;
|
||||||
|
|
||||||
use Symfony\Bundle\DebugBundle\DependencyInjection\Compiler\DumpDataCollectorPass;
|
use Symfony\Bundle\DebugBundle\DependencyInjection\Compiler\DumpDataCollectorPass;
|
||||||
|
use Symfony\Bundle\DebugBundle\DependencyInjection\Compiler\RemoveWebServerBundleLoggerPass;
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||||
@ -52,6 +53,7 @@ class DebugBundle extends Bundle
|
|||||||
parent::build($container);
|
parent::build($container);
|
||||||
|
|
||||||
$container->addCompilerPass(new DumpDataCollectorPass());
|
$container->addCompilerPass(new DumpDataCollectorPass());
|
||||||
|
$container->addCompilerPass(new RemoveWebServerBundleLoggerPass());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function registerCommands(Application $application)
|
public function registerCommands(Application $application)
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
<?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\Bundle\DebugBundle\DependencyInjection\Compiler;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||||
|
*/
|
||||||
|
class RemoveWebServerBundleLoggerPass implements CompilerPassInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function process(ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
if ($container->hasDefinition('web_server.command.server_log') && $container->hasDefinition('monolog.command.server_log')) {
|
||||||
|
$container->removeDefinition('web_server.command.server_log');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Bundle\DebugBundle\DependencyInjection;
|
namespace Symfony\Bundle\DebugBundle\DependencyInjection;
|
||||||
|
|
||||||
|
use Symfony\Bridge\Monolog\Command\ServerLogCommand;
|
||||||
use Symfony\Bundle\DebugBundle\Command\ServerDumpPlaceholderCommand;
|
use Symfony\Bundle\DebugBundle\Command\ServerDumpPlaceholderCommand;
|
||||||
use Symfony\Component\Config\FileLocator;
|
use Symfony\Component\Config\FileLocator;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
@ -90,6 +91,10 @@ class DebugExtension extends Extension
|
|||||||
]])
|
]])
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!class_exists(ServerLogCommand::class)) {
|
||||||
|
$container->removeDefinition('monolog.command.server_log');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -107,5 +107,9 @@
|
|||||||
</argument>
|
</argument>
|
||||||
<tag name="console.command" command="server:dump" />
|
<tag name="console.command" command="server:dump" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="monolog.command.server_log" class="Symfony\Bridge\Monolog\Command\ServerLogCommand">
|
||||||
|
<tag name="console.command" command="server:log" />
|
||||||
|
</service>
|
||||||
</services>
|
</services>
|
||||||
</container>
|
</container>
|
||||||
|
@ -26,7 +26,7 @@ use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
|||||||
/**
|
/**
|
||||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||||
*
|
*
|
||||||
* @deprecated since Symfony 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead.
|
* @deprecated since Symfony 4.4, to be removed in 5.0; use ServerLogCommand from symfony/monolog-bridge instead
|
||||||
*/
|
*/
|
||||||
class ServerLogCommand extends Command
|
class ServerLogCommand extends Command
|
||||||
{
|
{
|
||||||
@ -80,7 +80,7 @@ EOF
|
|||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output)
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
@trigger_error('Using the WebserverBundle is deprecated since Symfony 4.4. The new Symfony local server has more features, you can use it instead.', E_USER_DEPRECATED);
|
@trigger_error('Using the WebserverBundle is deprecated since Symfony 4.4. Use the DebugBundle combined with MonologBridge instead.', E_USER_DEPRECATED);
|
||||||
|
|
||||||
$filter = $input->getOption('filter');
|
$filter = $input->getOption('filter');
|
||||||
if ($filter) {
|
if ($filter) {
|
||||||
|
Reference in New Issue
Block a user