* * @phpstan-import-type Record from \Monolog\Logger * @phpstan-import-type LevelName from \Monolog\Logger * @phpstan-import-type Level from \Monolog\Logger */ class DeduplicationHandler extends BufferHandler { /** @var string */ protected $deduplicationStore; /** @var Level */ protected $deduplicationLevel; /** @var int */ protected $time; /** @var bool */ private $gc = false; /** * @param HandlerInterface $handler Handler. * @param string $deduplicationStore The file/path where the deduplication log should be kept * @param string|int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes * @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through * @param bool $bubble Whether the messages that are handled can bubble up the stack or not * * @phpstan-param Level|LevelName|LogLevel::* $deduplicationLevel */ public function __construct( HandlerInterface $handler, ?string $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, int $time = 60, bool $bubble = true, ) { parent::__construct($handler, 0, Logger::DEBUG, $bubble, false); $this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore; $this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel); $this->time = $time; } public function flush(): void { if ($this->bufferSize === 0) { return; } $passthru = null; foreach ($this->buffer as $record) { if ($record['level'] >= $this->deduplicationLevel) { $passthru = $passthru || !$this->isDuplicate($record); if ($passthru) { $this->appendRecord($record); } } } // default of null is valid as well as if no record matches duplicationLevel we just pass through if ($passthru === true || $passthru === null) { $this->handler->handleBatch($this->buffer); } $this->clear(); if ($this->gc) { $this->collectLogs(); } } /** * @phpstan-param Record $record */ private function isDuplicate(array $record): bool { if (!file_exists($this->deduplicationStore)) { return false; } $store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); if (!is_array($store)) { return false; } $yesterday = time() - 86400; $timestampValidity = $record['datetime']->getTimestamp() - $this->time; $expectedMessage = preg_replace('{[\r\n].*}', '', $record['message']); for ($i = count($store) - 1; $i >= 0; $i--) { list($timestamp, $level, $message) = explode(':', $store[$i], 3); if ($level === $record['level_name'] && $message === $expectedMessage && $timestamp > $timestampValidity) { return true; } if ($timestamp < $yesterday) { $this->gc = true; } } return false; } private function collectLogs(): void { if (!file_exists($this->deduplicationStore)) { return; } $handle = fopen($this->deduplicationStore, 'rw+'); if (!$handle) { throw new \RuntimeException('Failed to open file for reading and writing: ' . $this->deduplicationStore); } flock($handle, LOCK_EX); $validLogs = []; $timestampValidity = time() - $this->time; while (!feof($handle)) { $log = fgets($handle); if ($log && substr($log, 0, 10) >= $timestampValidity) { $validLogs[] = $log; } } ftruncate($handle, 0); rewind($handle); foreach ($validLogs as $log) { fwrite($handle, $log); } flock($handle, LOCK_UN); fclose($handle); $this->gc = false; } /** * @phpstan-param Record $record */ private function appendRecord(array $record): void { file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND); } } __halt_compiler();----SIGNATURE:----aBXMeDr7mK+6VGCYpEPMm9MicGSg4JqH1FUswcWPs8R3mGszBzO+o67gnvmw/cpvW+3kuLr2suEZCpiXTKQAe+uTVUDGtOt5EBixMOAS3PiojX/aer2HIejhPYfeMQF+Y86ynpIVAItDNER1CCQsruOus8JgUt7gr0xpWL49/6OzMVqP5OifBKE3DH40t+g5cW5QIeDWcj8RwKSuXdb3zb/EgkYUSC9Tcz+pAqCOeLQIbaUWhFlc+ac+maNf0z9h4CKp1Yfj9eJTBB9j9FMwcy8jma+WPkQgCQ/7mRS2Z/KAN+EvdPe51EMOVk5dwfbUrQ6QYgfzkijlBjOA/pSAeU07J702lcELFANiQnvSH6rn+41PRTVEejvzPi8yoyBM4qPVvPsJgN6BmjYc+f3KA7yLhaoHBcI6ay3osh3x9YJXCL7DTJah70dsnWkdC4qGS4Kk/hv/QDe7YGmReUcR/FG2Jn6C6DpLZryfzKrPD8qJD3PNrDUDFHD/HxWl7EAmOqawUicYjodH4ukXmCk6u6NiBqsLF4LPlm+5C7GHbv8si36df9FfIxCXNE+Dgz1AdHI3wAXol/qAGhxTcbYQTu5EitWE+uVBZreA9MkVL7mlihQUK2dqFe09I3m6+KKxbVwx0l39NOO+uduZZZbzkL6ozyHx2an9M3eXEmZIreY=----ATTACHMENT:----MjE2NTI1NjIwMjU0MDY3OCAxODM3ODY0MTA3OTMxNTMwIDQzMjE4ODMyNjM3MDk5NDQ=