]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / vendor / monolog / monolog / src / Monolog / Handler / DeduplicationHandler.php
1 <?php
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
12 namespace Monolog\Handler;
13
14 use Monolog\Logger;
15
16 /**
17  * Simple handler wrapper that deduplicates log records across multiple requests
18  *
19  * It also includes the BufferHandler functionality and will buffer
20  * all messages until the end of the request or flush() is called.
21  *
22  * This works by storing all log records' messages above $deduplicationLevel
23  * to the file specified by $deduplicationStore. When further logs come in at the end of the
24  * request (or when flush() is called), all those above $deduplicationLevel are checked
25  * against the existing stored logs. If they match and the timestamps in the stored log is
26  * not older than $time seconds, the new log record is discarded. If no log record is new, the
27  * whole data set is discarded.
28  *
29  * This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers
30  * that send messages to people, to avoid spamming with the same message over and over in case of
31  * a major component failure like a database server being down which makes all requests fail in the
32  * same way.
33  *
34  * @author Jordi Boggiano <j.boggiano@seld.be>
35  */
36 class DeduplicationHandler extends BufferHandler
37 {
38     /**
39      * @var string
40      */
41     protected $deduplicationStore;
42
43     /**
44      * @var int
45      */
46     protected $deduplicationLevel;
47
48     /**
49      * @var int
50      */
51     protected $time;
52
53     /**
54      * @var bool
55      */
56     private $gc = false;
57
58     /**
59      * @param HandlerInterface $handler            Handler.
60      * @param string           $deduplicationStore The file/path where the deduplication log should be kept
61      * @param int              $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes
62      * @param int              $time               The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through
63      * @param Boolean          $bubble             Whether the messages that are handled can bubble up the stack or not
64      */
65     public function __construct(HandlerInterface $handler, $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, $time = 60, $bubble = true)
66     {
67         parent::__construct($handler, 0, Logger::DEBUG, $bubble, false);
68
69         $this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore;
70         $this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel);
71         $this->time = $time;
72     }
73
74     public function flush()
75     {
76         if ($this->bufferSize === 0) {
77             return;
78         }
79
80         $passthru = null;
81
82         foreach ($this->buffer as $record) {
83             if ($record['level'] >= $this->deduplicationLevel) {
84
85                 $passthru = $passthru || !$this->isDuplicate($record);
86                 if ($passthru) {
87                     $this->appendRecord($record);
88                 }
89             }
90         }
91
92         // default of null is valid as well as if no record matches duplicationLevel we just pass through
93         if ($passthru === true || $passthru === null) {
94             $this->handler->handleBatch($this->buffer);
95         }
96
97         $this->clear();
98
99         if ($this->gc) {
100             $this->collectLogs();
101         }
102     }
103
104     private function isDuplicate(array $record)
105     {
106         if (!file_exists($this->deduplicationStore)) {
107             return false;
108         }
109
110         $store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
111         if (!is_array($store)) {
112             return false;
113         }
114
115         $yesterday = time() - 86400;
116         $timestampValidity = $record['datetime']->getTimestamp() - $this->time;
117         $expectedMessage = preg_replace('{[\r\n].*}', '', $record['message']);
118
119         for ($i = count($store) - 1; $i >= 0; $i--) {
120             list($timestamp, $level, $message) = explode(':', $store[$i], 3);
121
122             if ($level === $record['level_name'] && $message === $expectedMessage && $timestamp > $timestampValidity) {
123                 return true;
124             }
125
126             if ($timestamp < $yesterday) {
127                 $this->gc = true;
128             }
129         }
130
131         return false;
132     }
133
134     private function collectLogs()
135     {
136         if (!file_exists($this->deduplicationStore)) {
137             return false;
138         }
139
140         $handle = fopen($this->deduplicationStore, 'rw+');
141         flock($handle, LOCK_EX);
142         $validLogs = array();
143
144         $timestampValidity = time() - $this->time;
145
146         while (!feof($handle)) {
147             $log = fgets($handle);
148             if (substr($log, 0, 10) >= $timestampValidity) {
149                 $validLogs[] = $log;
150             }
151         }
152
153         ftruncate($handle, 0);
154         rewind($handle);
155         foreach ($validLogs as $log) {
156             fwrite($handle, $log);
157         }
158
159         flock($handle, LOCK_UN);
160         fclose($handle);
161
162         $this->gc = false;
163     }
164
165     private function appendRecord(array $record)
166     {
167         file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND);
168     }
169 }