Logger.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. <?php
  2. namespace League\CLImate;
  3. use Psr\Log\AbstractLogger;
  4. use Psr\Log\InvalidArgumentException;
  5. use Psr\Log\LogLevel;
  6. use function array_key_exists;
  7. use function is_array;
  8. use function str_replace;
  9. use function strpos;
  10. /**
  11. * A PSR-3 compatible logger that uses CLImate for output.
  12. */
  13. class Logger extends AbstractLogger
  14. {
  15. /**
  16. * @var array $levels Conversion of the level strings to their numeric representations.
  17. */
  18. private $levels = [
  19. LogLevel::EMERGENCY => 1,
  20. LogLevel::ALERT => 2,
  21. LogLevel::CRITICAL => 3,
  22. LogLevel::ERROR => 4,
  23. LogLevel::WARNING => 5,
  24. LogLevel::NOTICE => 6,
  25. LogLevel::INFO => 7,
  26. LogLevel::DEBUG => 8,
  27. ];
  28. /**
  29. * @var int $level Ignore logging attempts at a level less than this.
  30. */
  31. private $level;
  32. /**
  33. * @var CLImate $climate The underlying climate instance we are using for output.
  34. */
  35. private $climate;
  36. /**
  37. * Create a new Logger instance.
  38. *
  39. * @param string $level One of the LogLevel constants
  40. * @param CLImate $climate An existing CLImate instance to use for output
  41. */
  42. public function __construct($level = LogLevel::INFO, CLImate $climate = null)
  43. {
  44. $this->level = $this->convertLevel($level);
  45. if ($climate === null) {
  46. $climate = new CLImate();
  47. }
  48. $this->climate = $climate;
  49. # Define some default styles to use for the output
  50. $commands = [
  51. "emergency" => ["white", "bold", "background_red"],
  52. "alert" => ["white", "background_yellow"],
  53. "critical" => ["red", "bold"],
  54. "error" => ["red"],
  55. "warning" => "yellow",
  56. "notice" => "light_cyan",
  57. "info" => "green",
  58. "debug" => "dark_gray",
  59. ];
  60. # If any of the required styles are not defined then define them now
  61. foreach ($commands as $command => $style) {
  62. if (!$this->climate->style->get($command)) {
  63. $this->climate->style->addCommand($command, $style);
  64. }
  65. }
  66. }
  67. /**
  68. * Get a numeric log level for the passed parameter.
  69. *
  70. * @param string $level One of the LogLevel constants
  71. *
  72. * @return int
  73. */
  74. private function convertLevel($level)
  75. {
  76. # If this is one of the defined string log levels then return it's numeric value
  77. if (!array_key_exists($level, $this->levels)) {
  78. throw new InvalidArgumentException("Unknown log level: {$level}");
  79. }
  80. return $this->levels[$level];
  81. }
  82. /**
  83. * Get a new instance logging at a different level
  84. *
  85. * @param string $level One of the LogLevel constants
  86. *
  87. * @return Logger
  88. */
  89. public function withLogLevel($level)
  90. {
  91. $logger = clone $this;
  92. $logger->level = $this->convertLevel($level);
  93. return $logger;
  94. }
  95. /**
  96. * Log messages to a CLImate instance.
  97. *
  98. * @param string $level One of the LogLevel constants
  99. * @param string|object $message If an object is passed it must implement __toString()
  100. * @param array $context Placeholders to be substituted in the message
  101. *
  102. * @return void
  103. */
  104. public function log($level, $message, array $context = []): void
  105. {
  106. if ($this->convertLevel($level) > $this->level) {
  107. return;
  108. }
  109. # Handle objects implementing __toString
  110. $message = (string)$message;
  111. # Handle any placeholders in the $context array
  112. foreach ($context as $key => $val) {
  113. $placeholder = "{" . $key . "}";
  114. # If this context key is used as a placeholder, then replace it, and remove it from the $context array
  115. if (strpos($message, $placeholder) !== false) {
  116. $val = (string)$val;
  117. $message = str_replace($placeholder, $val, $message);
  118. unset($context[$key]);
  119. }
  120. }
  121. # Send the message to the climate instance
  122. $this->climate->{$level}($message);
  123. # Append any context information not used as placeholders
  124. $this->outputRecursiveContext($level, $context, 1);
  125. }
  126. /**
  127. * Handle recursive arrays in the logging context.
  128. *
  129. * @param string $level One of the LogLevel constants
  130. * @param array $context The array of context to output
  131. * @param int $indent The current level of indentation to be used
  132. *
  133. * @return void
  134. */
  135. private function outputRecursiveContext($level, array $context, $indent)
  136. {
  137. foreach ($context as $key => $val) {
  138. $this->climate->tab($indent);
  139. $this->climate->{$level}()->inline("{$key}: ");
  140. if (is_array($val)) {
  141. $this->climate->{$level}("[");
  142. $this->outputRecursiveContext($level, $val, $indent + 1);
  143. $this->climate->tab($indent)->{$level}("]");
  144. } else {
  145. $this->climate->{$level}((string)$val);
  146. }
  147. }
  148. }
  149. }