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\ResettableInterface; 16use Monolog\Formatter\FormatterInterface; 17 18/** 19 * Buffers all records until closing the handler and then pass them as batch. 20 * 21 * This is useful for a MailHandler to send only one mail per request instead of 22 * sending one per log message. 23 * 24 * @author Christophe Coevoet <stof@notk.org> 25 * 26 * @phpstan-import-type Record from \Monolog\Logger 27 */ 28class BufferHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface 29{ 30 use ProcessableHandlerTrait; 31 32 /** @var HandlerInterface */ 33 protected $handler; 34 /** @var int */ 35 protected $bufferSize = 0; 36 /** @var int */ 37 protected $bufferLimit; 38 /** @var bool */ 39 protected $flushOnOverflow; 40 /** @var Record[] */ 41 protected $buffer = []; 42 /** @var bool */ 43 protected $initialized = false; 44 45 /** 46 * @param HandlerInterface $handler Handler. 47 * @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. 48 * @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded 49 */ 50 public function __construct(HandlerInterface $handler, int $bufferLimit = 0, $level = Logger::DEBUG, bool $bubble = true, bool $flushOnOverflow = false) 51 { 52 parent::__construct($level, $bubble); 53 $this->handler = $handler; 54 $this->bufferLimit = $bufferLimit; 55 $this->flushOnOverflow = $flushOnOverflow; 56 } 57 58 /** 59 * {@inheritDoc} 60 */ 61 public function handle(array $record): bool 62 { 63 if ($record['level'] < $this->level) { 64 return false; 65 } 66 67 if (!$this->initialized) { 68 // __destructor() doesn't get called on Fatal errors 69 register_shutdown_function([$this, 'close']); 70 $this->initialized = true; 71 } 72 73 if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) { 74 if ($this->flushOnOverflow) { 75 $this->flush(); 76 } else { 77 array_shift($this->buffer); 78 $this->bufferSize--; 79 } 80 } 81 82 if ($this->processors) { 83 /** @var Record $record */ 84 $record = $this->processRecord($record); 85 } 86 87 $this->buffer[] = $record; 88 $this->bufferSize++; 89 90 return false === $this->bubble; 91 } 92 93 public function flush(): void 94 { 95 if ($this->bufferSize === 0) { 96 return; 97 } 98 99 $this->handler->handleBatch($this->buffer); 100 $this->clear(); 101 } 102 103 public function __destruct() 104 { 105 // suppress the parent behavior since we already have register_shutdown_function() 106 // to call close(), and the reference contained there will prevent this from being 107 // GC'd until the end of the request 108 } 109 110 /** 111 * {@inheritDoc} 112 */ 113 public function close(): void 114 { 115 $this->flush(); 116 117 $this->handler->close(); 118 } 119 120 /** 121 * Clears the buffer without flushing any messages down to the wrapped handler. 122 */ 123 public function clear(): void 124 { 125 $this->bufferSize = 0; 126 $this->buffer = []; 127 } 128 129 public function reset() 130 { 131 $this->flush(); 132 133 parent::reset(); 134 135 $this->resetProcessors(); 136 137 if ($this->handler instanceof ResettableInterface) { 138 $this->handler->reset(); 139 } 140 } 141 142 /** 143 * {@inheritDoc} 144 */ 145 public function setFormatter(FormatterInterface $formatter): HandlerInterface 146 { 147 if ($this->handler instanceof FormattableHandlerInterface) { 148 $this->handler->setFormatter($formatter); 149 150 return $this; 151 } 152 153 throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); 154 } 155 156 /** 157 * {@inheritDoc} 158 */ 159 public function getFormatter(): FormatterInterface 160 { 161 if ($this->handler instanceof FormattableHandlerInterface) { 162 return $this->handler->getFormatter(); 163 } 164 165 throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); 166 } 167} 168