1<?php declare(strict_types=1); 2 3/* 4 * This file is part of the Monolog package. 5 * 6 * (c) Jordi Boggiano <j.boggiano@seld.be> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Monolog\Handler; 13 14use Monolog\Logger; 15use Monolog\Formatter\FormatterInterface; 16 17/** 18 * Handler to only pass log messages when a certain threshold of number of messages is reached. 19 * 20 * This can be useful in cases of processing a batch of data, but you're for example only interested 21 * in case it fails catastrophically instead of a warning for 1 or 2 events. Worse things can happen, right? 22 * 23 * Usage example: 24 * 25 * ``` 26 * $log = new Logger('application'); 27 * $handler = new SomeHandler(...) 28 * 29 * // Pass all warnings to the handler when more than 10 & all error messages when more then 5 30 * $overflow = new OverflowHandler($handler, [Logger::WARNING => 10, Logger::ERROR => 5]); 31 * 32 * $log->pushHandler($overflow); 33 *``` 34 * 35 * @author Kris Buist <krisbuist@gmail.com> 36 */ 37class OverflowHandler extends AbstractHandler implements FormattableHandlerInterface 38{ 39 /** @var HandlerInterface */ 40 private $handler; 41 42 /** @var int[] */ 43 private $thresholdMap = [ 44 Logger::DEBUG => 0, 45 Logger::INFO => 0, 46 Logger::NOTICE => 0, 47 Logger::WARNING => 0, 48 Logger::ERROR => 0, 49 Logger::CRITICAL => 0, 50 Logger::ALERT => 0, 51 Logger::EMERGENCY => 0, 52 ]; 53 54 /** 55 * Buffer of all messages passed to the handler before the threshold was reached 56 * 57 * @var mixed[][] 58 */ 59 private $buffer = []; 60 61 /** 62 * @param HandlerInterface $handler 63 * @param int[] $thresholdMap Dictionary of logger level => threshold 64 * @param int|string $level The minimum logging level at which this handler will be triggered 65 * @param bool $bubble 66 */ 67 public function __construct( 68 HandlerInterface $handler, 69 array $thresholdMap = [], 70 $level = Logger::DEBUG, 71 bool $bubble = true 72 ) { 73 $this->handler = $handler; 74 foreach ($thresholdMap as $thresholdLevel => $threshold) { 75 $this->thresholdMap[$thresholdLevel] = $threshold; 76 } 77 parent::__construct($level, $bubble); 78 } 79 80 /** 81 * Handles a record. 82 * 83 * All records may be passed to this method, and the handler should discard 84 * those that it does not want to handle. 85 * 86 * The return value of this function controls the bubbling process of the handler stack. 87 * Unless the bubbling is interrupted (by returning true), the Logger class will keep on 88 * calling further handlers in the stack with a given log record. 89 * 90 * @param array $record The record to handle 91 * 92 * @return Boolean true means that this handler handled the record, and that bubbling is not permitted. 93 * false means the record was either not processed or that this handler allows bubbling. 94 */ 95 public function handle(array $record): bool 96 { 97 if ($record['level'] < $this->level) { 98 return false; 99 } 100 101 $level = $record['level']; 102 103 if (!isset($this->thresholdMap[$level])) { 104 $this->thresholdMap[$level] = 0; 105 } 106 107 if ($this->thresholdMap[$level] > 0) { 108 // The overflow threshold is not yet reached, so we're buffering the record and lowering the threshold by 1 109 $this->thresholdMap[$level]--; 110 $this->buffer[$level][] = $record; 111 112 return false === $this->bubble; 113 } 114 115 if ($this->thresholdMap[$level] == 0) { 116 // This current message is breaking the threshold. Flush the buffer and continue handling the current record 117 foreach ($this->buffer[$level] ?? [] as $buffered) { 118 $this->handler->handle($buffered); 119 } 120 $this->thresholdMap[$level]--; 121 unset($this->buffer[$level]); 122 } 123 124 $this->handler->handle($record); 125 126 return false === $this->bubble; 127 } 128 129 /** 130 * {@inheritdoc} 131 */ 132 public function setFormatter(FormatterInterface $formatter): HandlerInterface 133 { 134 if ($this->handler instanceof FormattableHandlerInterface) { 135 $this->handler->setFormatter($formatter); 136 137 return $this; 138 } 139 140 throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); 141 } 142 143 /** 144 * {@inheritdoc} 145 */ 146 public function getFormatter(): FormatterInterface 147 { 148 if ($this->handler instanceof FormattableHandlerInterface) { 149 return $this->handler->getFormatter(); 150 } 151 152 throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); 153 } 154} 155