ConsoleLogger.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\Migrations\Tools\Console;
  4. use DateTime;
  5. use DateTimeInterface;
  6. use Psr\Log\AbstractLogger;
  7. use Psr\Log\InvalidArgumentException;
  8. use Psr\Log\LogLevel;
  9. use Symfony\Component\Console\Output\ConsoleOutputInterface;
  10. use Symfony\Component\Console\Output\OutputInterface;
  11. use function get_class;
  12. use function gettype;
  13. use function is_object;
  14. use function is_scalar;
  15. use function method_exists;
  16. use function sprintf;
  17. use function strpos;
  18. use function strtr;
  19. /**
  20. * PSR-3 compliant console logger.
  21. *
  22. * @internal
  23. *
  24. * @see https://www.php-fig.org/psr/psr-3/
  25. */
  26. final class ConsoleLogger extends AbstractLogger
  27. {
  28. public const INFO = 'info';
  29. public const ERROR = 'error';
  30. /** @var OutputInterface */
  31. private $output;
  32. /** @var array<string, int> */
  33. private $verbosityLevelMap = [
  34. LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL,
  35. LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL,
  36. LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL,
  37. LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL,
  38. LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL,
  39. LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL,
  40. LogLevel::INFO => OutputInterface::VERBOSITY_VERBOSE,
  41. LogLevel::DEBUG => OutputInterface::VERBOSITY_VERY_VERBOSE,
  42. ];
  43. /** @var array<string, string> */
  44. private $formatLevelMap = [
  45. LogLevel::EMERGENCY => self::ERROR,
  46. LogLevel::ALERT => self::ERROR,
  47. LogLevel::CRITICAL => self::ERROR,
  48. LogLevel::ERROR => self::ERROR,
  49. LogLevel::WARNING => self::INFO,
  50. LogLevel::NOTICE => self::INFO,
  51. LogLevel::INFO => self::INFO,
  52. LogLevel::DEBUG => self::INFO,
  53. ];
  54. /**
  55. * @param array<string, int> $verbosityLevelMap
  56. * @param array<string, string> $formatLevelMap
  57. */
  58. public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = [])
  59. {
  60. $this->output = $output;
  61. $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
  62. $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
  63. }
  64. /**
  65. * {@inheritdoc}
  66. */
  67. public function log($level, $message, array $context = []): void
  68. {
  69. if (! isset($this->verbosityLevelMap[$level])) {
  70. throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
  71. }
  72. $output = $this->output;
  73. // Write to the error output if necessary and available
  74. if ($this->formatLevelMap[$level] === self::ERROR) {
  75. if ($this->output instanceof ConsoleOutputInterface) {
  76. $output = $output->getErrorOutput();
  77. }
  78. }
  79. // the if condition check isn't necessary -- it's the same one that $output will do internally anyway.
  80. // We only do it for efficiency here as the message formatting is relatively expensive.
  81. if ($output->getVerbosity() < $this->verbosityLevelMap[$level]) {
  82. return;
  83. }
  84. $output->writeln(sprintf('<%1$s>[%2$s] %3$s</%1$s>', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]);
  85. }
  86. /**
  87. * Interpolates context values into the message placeholders.
  88. *
  89. * @param mixed[] $context
  90. */
  91. private function interpolate(string $message, array $context): string
  92. {
  93. if (strpos($message, '{') === false) {
  94. return $message;
  95. }
  96. $replacements = [];
  97. foreach ($context as $key => $val) {
  98. if ($val === null || is_scalar($val) || (is_object($val) && method_exists($val, '__toString'))) {
  99. $replacements["{{$key}}"] = $val;
  100. } elseif ($val instanceof DateTimeInterface) {
  101. $replacements["{{$key}}"] = $val->format(DateTime::RFC3339);
  102. } elseif (is_object($val)) {
  103. $replacements["{{$key}}"] = '[object ' . get_class($val) . ']';
  104. } else {
  105. $replacements["{{$key}}"] = '[' . gettype($val) . ']';
  106. }
  107. if (! isset($replacements["{{$key}}"])) {
  108. continue;
  109. }
  110. $replacements["{{$key}}"] = '<comment>' . $replacements["{{$key}}"] . '</comment>';
  111. }
  112. return strtr($message, $replacements);
  113. }
  114. }