FingersCrossedHandler.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  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 Closure;
  12. use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy;
  13. use Monolog\Handler\FingersCrossed\ActivationStrategyInterface;
  14. use Monolog\Level;
  15. use Monolog\Logger;
  16. use Monolog\ResettableInterface;
  17. use Monolog\Formatter\FormatterInterface;
  18. use Psr\Log\LogLevel;
  19. use Monolog\LogRecord;
  20. /**
  21. * Buffers all records until a certain level is reached
  22. *
  23. * The advantage of this approach is that you don't get any clutter in your log files.
  24. * Only requests which actually trigger an error (or whatever your actionLevel is) will be
  25. * in the logs, but they will contain all records, not only those above the level threshold.
  26. *
  27. * You can then have a passthruLevel as well which means that at the end of the request,
  28. * even if it did not get activated, it will still send through log records of e.g. at least a
  29. * warning level.
  30. *
  31. * You can find the various activation strategies in the
  32. * Monolog\Handler\FingersCrossed\ namespace.
  33. *
  34. * @author Jordi Boggiano <j.boggiano@seld.be>
  35. */
  36. class FingersCrossedHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface
  37. {
  38. use ProcessableHandlerTrait;
  39. /**
  40. * Handler or factory Closure($record, $this)
  41. *
  42. * @phpstan-var (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface
  43. */
  44. protected Closure|HandlerInterface $handler;
  45. protected ActivationStrategyInterface $activationStrategy;
  46. protected bool $buffering = true;
  47. protected int $bufferSize;
  48. /** @var LogRecord[] */
  49. protected array $buffer = [];
  50. protected bool $stopBuffering;
  51. protected Level|null $passthruLevel = null;
  52. protected bool $bubble;
  53. /**
  54. * @phpstan-param (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface $handler
  55. *
  56. * @param Closure|HandlerInterface $handler Handler or factory Closure($record|null, $fingersCrossedHandler).
  57. * @param int|string|Level|LogLevel::* $activationStrategy Strategy which determines when this handler takes action, or a level name/value at which the handler is activated
  58. * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
  59. * @param bool $bubble Whether the messages that are handled can bubble up the stack or not
  60. * @param bool $stopBuffering Whether the handler should stop buffering after being triggered (default true)
  61. * @param int|string|Level|LogLevel::*|null $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered
  62. *
  63. * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::*|ActivationStrategyInterface $activationStrategy
  64. * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $passthruLevel
  65. */
  66. public function __construct(Closure|HandlerInterface $handler, int|string|Level|ActivationStrategyInterface $activationStrategy = null, int $bufferSize = 0, bool $bubble = true, bool $stopBuffering = true, int|string|Level|null $passthruLevel = null)
  67. {
  68. if (null === $activationStrategy) {
  69. $activationStrategy = new ErrorLevelActivationStrategy(Level::Warning);
  70. }
  71. // convert simple int activationStrategy to an object
  72. if (!$activationStrategy instanceof ActivationStrategyInterface) {
  73. $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy);
  74. }
  75. $this->handler = $handler;
  76. $this->activationStrategy = $activationStrategy;
  77. $this->bufferSize = $bufferSize;
  78. $this->bubble = $bubble;
  79. $this->stopBuffering = $stopBuffering;
  80. if ($passthruLevel !== null) {
  81. $this->passthruLevel = Logger::toMonologLevel($passthruLevel);
  82. }
  83. }
  84. /**
  85. * @inheritDoc
  86. */
  87. public function isHandling(LogRecord $record): bool
  88. {
  89. return true;
  90. }
  91. /**
  92. * Manually activate this logger regardless of the activation strategy
  93. */
  94. public function activate(): void
  95. {
  96. if ($this->stopBuffering) {
  97. $this->buffering = false;
  98. }
  99. $this->getHandler(end($this->buffer) ?: null)->handleBatch($this->buffer);
  100. $this->buffer = [];
  101. }
  102. /**
  103. * @inheritDoc
  104. */
  105. public function handle(LogRecord $record): bool
  106. {
  107. if (\count($this->processors) > 0) {
  108. $record = $this->processRecord($record);
  109. }
  110. if ($this->buffering) {
  111. $this->buffer[] = $record;
  112. if ($this->bufferSize > 0 && \count($this->buffer) > $this->bufferSize) {
  113. array_shift($this->buffer);
  114. }
  115. if ($this->activationStrategy->isHandlerActivated($record)) {
  116. $this->activate();
  117. }
  118. } else {
  119. $this->getHandler($record)->handle($record);
  120. }
  121. return false === $this->bubble;
  122. }
  123. /**
  124. * @inheritDoc
  125. */
  126. public function close(): void
  127. {
  128. $this->flushBuffer();
  129. $this->getHandler()->close();
  130. }
  131. public function reset(): void
  132. {
  133. $this->flushBuffer();
  134. $this->resetProcessors();
  135. if ($this->getHandler() instanceof ResettableInterface) {
  136. $this->getHandler()->reset();
  137. }
  138. }
  139. /**
  140. * Clears the buffer without flushing any messages down to the wrapped handler.
  141. *
  142. * It also resets the handler to its initial buffering state.
  143. */
  144. public function clear(): void
  145. {
  146. $this->buffer = [];
  147. $this->reset();
  148. }
  149. /**
  150. * Resets the state of the handler. Stops forwarding records to the wrapped handler.
  151. */
  152. private function flushBuffer(): void
  153. {
  154. if (null !== $this->passthruLevel) {
  155. $passthruLevel = $this->passthruLevel;
  156. $this->buffer = array_filter($this->buffer, static function ($record) use ($passthruLevel) {
  157. return $passthruLevel->includes($record->level);
  158. });
  159. if (\count($this->buffer) > 0) {
  160. $this->getHandler(end($this->buffer))->handleBatch($this->buffer);
  161. }
  162. }
  163. $this->buffer = [];
  164. $this->buffering = true;
  165. }
  166. /**
  167. * Return the nested handler
  168. *
  169. * If the handler was provided as a factory, this will trigger the handler's instantiation.
  170. */
  171. public function getHandler(LogRecord|null $record = null): HandlerInterface
  172. {
  173. if (!$this->handler instanceof HandlerInterface) {
  174. $handler = ($this->handler)($record, $this);
  175. if (!$handler instanceof HandlerInterface) {
  176. throw new \RuntimeException("The factory Closure should return a HandlerInterface");
  177. }
  178. $this->handler = $handler;
  179. }
  180. return $this->handler;
  181. }
  182. /**
  183. * @inheritDoc
  184. */
  185. public function setFormatter(FormatterInterface $formatter): HandlerInterface
  186. {
  187. $handler = $this->getHandler();
  188. if ($handler instanceof FormattableHandlerInterface) {
  189. $handler->setFormatter($formatter);
  190. return $this;
  191. }
  192. throw new \UnexpectedValueException('The nested handler of type '.\get_class($handler).' does not support formatters.');
  193. }
  194. /**
  195. * @inheritDoc
  196. */
  197. public function getFormatter(): FormatterInterface
  198. {
  199. $handler = $this->getHandler();
  200. if ($handler instanceof FormattableHandlerInterface) {
  201. return $handler->getFormatter();
  202. }
  203. throw new \UnexpectedValueException('The nested handler of type '.\get_class($handler).' does not support formatters.');
  204. }
  205. }