TestHandler.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of the Monolog package.
  4. *
  5. * (c) Jordi Boggiano <j.boggiano@seld.be>
  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 Monolog\Handler;
  11. use Monolog\Level;
  12. use Monolog\LevelName;
  13. use Monolog\Logger;
  14. use Psr\Log\LogLevel;
  15. use Monolog\LogRecord;
  16. /**
  17. * Used for testing purposes.
  18. *
  19. * It records all records and gives you access to them for verification.
  20. *
  21. * @author Jordi Boggiano <j.boggiano@seld.be>
  22. *
  23. * @method bool hasEmergency($record)
  24. * @method bool hasAlert($record)
  25. * @method bool hasCritical($record)
  26. * @method bool hasError($record)
  27. * @method bool hasWarning($record)
  28. * @method bool hasNotice($record)
  29. * @method bool hasInfo($record)
  30. * @method bool hasDebug($record)
  31. *
  32. * @method bool hasEmergencyRecords()
  33. * @method bool hasAlertRecords()
  34. * @method bool hasCriticalRecords()
  35. * @method bool hasErrorRecords()
  36. * @method bool hasWarningRecords()
  37. * @method bool hasNoticeRecords()
  38. * @method bool hasInfoRecords()
  39. * @method bool hasDebugRecords()
  40. *
  41. * @method bool hasEmergencyThatContains($message)
  42. * @method bool hasAlertThatContains($message)
  43. * @method bool hasCriticalThatContains($message)
  44. * @method bool hasErrorThatContains($message)
  45. * @method bool hasWarningThatContains($message)
  46. * @method bool hasNoticeThatContains($message)
  47. * @method bool hasInfoThatContains($message)
  48. * @method bool hasDebugThatContains($message)
  49. *
  50. * @method bool hasEmergencyThatMatches($message)
  51. * @method bool hasAlertThatMatches($message)
  52. * @method bool hasCriticalThatMatches($message)
  53. * @method bool hasErrorThatMatches($message)
  54. * @method bool hasWarningThatMatches($message)
  55. * @method bool hasNoticeThatMatches($message)
  56. * @method bool hasInfoThatMatches($message)
  57. * @method bool hasDebugThatMatches($message)
  58. *
  59. * @method bool hasEmergencyThatPasses($message)
  60. * @method bool hasAlertThatPasses($message)
  61. * @method bool hasCriticalThatPasses($message)
  62. * @method bool hasErrorThatPasses($message)
  63. * @method bool hasWarningThatPasses($message)
  64. * @method bool hasNoticeThatPasses($message)
  65. * @method bool hasInfoThatPasses($message)
  66. * @method bool hasDebugThatPasses($message)
  67. */
  68. class TestHandler extends AbstractProcessingHandler
  69. {
  70. /** @var LogRecord[] */
  71. protected $records = [];
  72. /** @phpstan-var array<value-of<Level::VALUES>, LogRecord[]> */
  73. protected array $recordsByLevel = [];
  74. /** @var bool */
  75. private $skipReset = false;
  76. /**
  77. * @return array<LogRecord>
  78. */
  79. public function getRecords()
  80. {
  81. return $this->records;
  82. }
  83. /**
  84. * @return void
  85. */
  86. public function clear()
  87. {
  88. $this->records = [];
  89. $this->recordsByLevel = [];
  90. }
  91. /**
  92. * @return void
  93. */
  94. public function reset()
  95. {
  96. if (!$this->skipReset) {
  97. $this->clear();
  98. }
  99. }
  100. /**
  101. * @return void
  102. */
  103. public function setSkipReset(bool $skipReset)
  104. {
  105. $this->skipReset = $skipReset;
  106. }
  107. /**
  108. * @param int|string|Level|LevelName|LogLevel::* $level Logging level value or name
  109. *
  110. * @phpstan-param value-of<Level::VALUES>|value-of<LevelName::VALUES>|Level|LevelName|LogLevel::* $level
  111. */
  112. public function hasRecords(int|string|Level|LevelName $level): bool
  113. {
  114. return isset($this->recordsByLevel[Logger::toMonologLevel($level)->value]);
  115. }
  116. /**
  117. * @param string|array $recordAssertions Either a message string or an array containing message and optionally context keys that will be checked against all records
  118. *
  119. * @phpstan-param array{message: string, context?: mixed[]}|string $recordAssertions
  120. */
  121. public function hasRecord(string|array $recordAssertions, Level $level): bool
  122. {
  123. if (is_string($recordAssertions)) {
  124. $recordAssertions = ['message' => $recordAssertions];
  125. }
  126. return $this->hasRecordThatPasses(function (LogRecord $rec) use ($recordAssertions) {
  127. if ($rec->message !== $recordAssertions['message']) {
  128. return false;
  129. }
  130. if (isset($recordAssertions['context']) && $rec->context !== $recordAssertions['context']) {
  131. return false;
  132. }
  133. return true;
  134. }, $level);
  135. }
  136. public function hasRecordThatContains(string $message, Level $level): bool
  137. {
  138. return $this->hasRecordThatPasses(fn (LogRecord $rec) => str_contains($rec->message, $message), $level);
  139. }
  140. public function hasRecordThatMatches(string $regex, Level $level): bool
  141. {
  142. return $this->hasRecordThatPasses(fn (LogRecord $rec) => preg_match($regex, $rec->message) > 0, $level);
  143. }
  144. /**
  145. * @phpstan-param callable(LogRecord, int): mixed $predicate
  146. */
  147. public function hasRecordThatPasses(callable $predicate, Level $level): bool
  148. {
  149. $level = Logger::toMonologLevel($level);
  150. if (!isset($this->recordsByLevel[$level->value])) {
  151. return false;
  152. }
  153. foreach ($this->recordsByLevel[$level->value] as $i => $rec) {
  154. if ($predicate($rec, $i)) {
  155. return true;
  156. }
  157. }
  158. return false;
  159. }
  160. /**
  161. * {@inheritDoc}
  162. */
  163. protected function write(LogRecord $record): void
  164. {
  165. $this->recordsByLevel[$record->level->value][] = $record;
  166. $this->records[] = $record;
  167. }
  168. /**
  169. * @param string $method
  170. * @param mixed[] $args
  171. * @return bool
  172. */
  173. public function __call($method, $args)
  174. {
  175. if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) {
  176. $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3];
  177. $level = constant(Level::class.'::' . $matches[2]);
  178. $callback = [$this, $genericMethod];
  179. if (is_callable($callback)) {
  180. $args[] = $level;
  181. return call_user_func_array($callback, $args);
  182. }
  183. }
  184. throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()');
  185. }
  186. }