vendor/symfony/monolog-bridge/Handler/ConsoleHandler.php line 158

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Bridge\Monolog\Handler;
  11. use Monolog\Formatter\FormatterInterface;
  12. use Monolog\Formatter\LineFormatter;
  13. use Monolog\Handler\AbstractProcessingHandler;
  14. use Monolog\Logger;
  15. use Monolog\LogRecord;
  16. use Symfony\Bridge\Monolog\Formatter\ConsoleFormatter;
  17. use Symfony\Component\Console\ConsoleEvents;
  18. use Symfony\Component\Console\Event\ConsoleCommandEvent;
  19. use Symfony\Component\Console\Event\ConsoleTerminateEvent;
  20. use Symfony\Component\Console\Output\ConsoleOutputInterface;
  21. use Symfony\Component\Console\Output\OutputInterface;
  22. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  23. use Symfony\Component\VarDumper\Dumper\CliDumper;
  24. if (Logger::API >= 3) {
  25. /**
  26. * The base class for compatibility between Monolog 3 LogRecord and Monolog 1/2 array records.
  27. *
  28. * @author Jordi Boggiano <j.boggiano@seld.be>
  29. *
  30. * @internal
  31. */
  32. trait CompatibilityIsHandlingHandler
  33. {
  34. abstract private function doIsHandling(array|LogRecord $record): bool;
  35. public function isHandling(LogRecord $record): bool
  36. {
  37. return $this->doIsHandling($record);
  38. }
  39. }
  40. } else {
  41. /**
  42. * The base class for compatibility between Monolog 3 LogRecord and Monolog 1/2 array records.
  43. *
  44. * @author Jordi Boggiano <j.boggiano@seld.be>
  45. *
  46. * @internal
  47. */
  48. trait CompatibilityIsHandlingHandler
  49. {
  50. abstract private function doIsHandling(array|LogRecord $record): bool;
  51. public function isHandling(array $record): bool
  52. {
  53. return $this->doIsHandling($record);
  54. }
  55. }
  56. }
  57. /**
  58. * Writes logs to the console output depending on its verbosity setting.
  59. *
  60. * It is disabled by default and gets activated as soon as a command is executed.
  61. * Instead of listening to the console events, the output can also be set manually.
  62. *
  63. * The minimum logging level at which this handler will be triggered depends on the
  64. * verbosity setting of the console output. The default mapping is:
  65. * - OutputInterface::VERBOSITY_NORMAL will show all WARNING and higher logs
  66. * - OutputInterface::VERBOSITY_VERBOSE (-v) will show all NOTICE and higher logs
  67. * - OutputInterface::VERBOSITY_VERY_VERBOSE (-vv) will show all INFO and higher logs
  68. * - OutputInterface::VERBOSITY_DEBUG (-vvv) will show all DEBUG and higher logs, i.e. all logs
  69. *
  70. * This mapping can be customized with the $verbosityLevelMap constructor parameter.
  71. *
  72. * @author Tobias Schultze <http://tobion.de>
  73. *
  74. * @final since Symfony 6.1
  75. */
  76. class ConsoleHandler extends AbstractProcessingHandler implements EventSubscriberInterface
  77. {
  78. use CompatibilityHandler;
  79. use CompatibilityIsHandlingHandler;
  80. use CompatibilityProcessingHandler;
  81. private ?OutputInterface $output;
  82. private array $verbosityLevelMap = [
  83. OutputInterface::VERBOSITY_QUIET => Logger::ERROR,
  84. OutputInterface::VERBOSITY_NORMAL => Logger::WARNING,
  85. OutputInterface::VERBOSITY_VERBOSE => Logger::NOTICE,
  86. OutputInterface::VERBOSITY_VERY_VERBOSE => Logger::INFO,
  87. OutputInterface::VERBOSITY_DEBUG => Logger::DEBUG,
  88. ];
  89. private array $consoleFormatterOptions;
  90. private int $nestedCommandDepth = 0;
  91. /**
  92. * @param OutputInterface|null $output The console output to use (the handler remains disabled when passing null
  93. * until the output is set, e.g. by using console events)
  94. * @param bool $bubble Whether the messages that are handled can bubble up the stack
  95. * @param array $verbosityLevelMap Array that maps the OutputInterface verbosity to a minimum logging
  96. * level (leave empty to use the default mapping)
  97. */
  98. public function __construct(?OutputInterface $output = null, bool $bubble = true, array $verbosityLevelMap = [], array $consoleFormatterOptions = [])
  99. {
  100. parent::__construct(Logger::DEBUG, $bubble);
  101. $this->output = $output;
  102. if ($verbosityLevelMap) {
  103. $this->verbosityLevelMap = $verbosityLevelMap;
  104. }
  105. $this->consoleFormatterOptions = $consoleFormatterOptions;
  106. }
  107. private function doIsHandling(array|LogRecord $record): bool
  108. {
  109. return $this->updateLevel() && parent::isHandling($record);
  110. }
  111. private function doHandle(array|LogRecord $record): bool
  112. {
  113. // we have to update the logging level each time because the verbosity of the
  114. // console output might have changed in the meantime (it is not immutable)
  115. return $this->updateLevel() && parent::handle($record);
  116. }
  117. /**
  118. * Sets the console output to use for printing logs.
  119. *
  120. * @return void
  121. */
  122. public function setOutput(OutputInterface $output)
  123. {
  124. $this->output = $output;
  125. }
  126. /**
  127. * Disables the output.
  128. */
  129. public function close(): void
  130. {
  131. $this->nestedCommandDepth = 0;
  132. $this->output = null;
  133. parent::close();
  134. }
  135. /**
  136. * Before a command is executed, the handler gets activated and the console output
  137. * is set in order to know where to write the logs.
  138. *
  139. * @return void
  140. */
  141. public function onCommand(ConsoleCommandEvent $event)
  142. {
  143. if (1 !== ++$this->nestedCommandDepth) {
  144. return;
  145. }
  146. $output = $event->getOutput();
  147. if ($output instanceof ConsoleOutputInterface) {
  148. $output = $output->getErrorOutput();
  149. }
  150. $this->setOutput($output);
  151. }
  152. /**
  153. * After a command has been executed, it disables the output.
  154. *
  155. * @return void
  156. */
  157. public function onTerminate(ConsoleTerminateEvent $event)
  158. {
  159. if ($this->nestedCommandDepth && !--$this->nestedCommandDepth) {
  160. $this->close();
  161. }
  162. }
  163. public static function getSubscribedEvents(): array
  164. {
  165. return [
  166. ConsoleEvents::COMMAND => ['onCommand', 255],
  167. ConsoleEvents::TERMINATE => ['onTerminate', -255],
  168. ];
  169. }
  170. private function doWrite(array|LogRecord $record): void
  171. {
  172. // at this point we've determined for sure that we want to output the record, so use the output's own verbosity
  173. $this->output->write((string) $record['formatted'], false, $this->output->getVerbosity());
  174. }
  175. protected function getDefaultFormatter(): FormatterInterface
  176. {
  177. if (!class_exists(CliDumper::class)) {
  178. return new LineFormatter();
  179. }
  180. if (!$this->output) {
  181. return new ConsoleFormatter($this->consoleFormatterOptions);
  182. }
  183. return new ConsoleFormatter(array_replace([
  184. 'colors' => $this->output->isDecorated(),
  185. 'multiline' => OutputInterface::VERBOSITY_DEBUG <= $this->output->getVerbosity(),
  186. ], $this->consoleFormatterOptions));
  187. }
  188. /**
  189. * Updates the logging level based on the verbosity setting of the console output.
  190. *
  191. * @return bool Whether the handler is enabled and verbosity is not set to quiet
  192. */
  193. private function updateLevel(): bool
  194. {
  195. if (null === $this->output) {
  196. return false;
  197. }
  198. $verbosity = $this->output->getVerbosity();
  199. if (isset($this->verbosityLevelMap[$verbosity])) {
  200. $this->setLevel($this->verbosityLevelMap[$verbosity]);
  201. } else {
  202. $this->setLevel(Logger::DEBUG);
  203. }
  204. return true;
  205. }
  206. }