PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / trunk
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance vtrunk
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / vendor / monolog / monolog / src / Monolog / Handler / DeduplicationHandler.php

DeduplicationHandler.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance trunk, at vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php

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