]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / vendor / monolog / monolog / src / Monolog / Handler / SocketHandler.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  * Stores to any socket - uses fsockopen() or pfsockopen().
18  *
19  * @author Pablo de Leon Belloc <pablolb@gmail.com>
20  * @see    http://php.net/manual/en/function.fsockopen.php
21  */
22 class SocketHandler extends AbstractProcessingHandler
23 {
24     private $connectionString;
25     private $connectionTimeout;
26     private $resource;
27     private $timeout = 0;
28     private $writingTimeout = 10;
29     private $lastSentBytes = null;
30     private $persistent = false;
31     private $errno;
32     private $errstr;
33     private $lastWritingAt;
34
35     /**
36      * @param string  $connectionString Socket connection string
37      * @param int     $level            The minimum logging level at which this handler will be triggered
38      * @param Boolean $bubble           Whether the messages that are handled can bubble up the stack or not
39      */
40     public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true)
41     {
42         parent::__construct($level, $bubble);
43         $this->connectionString = $connectionString;
44         $this->connectionTimeout = (float) ini_get('default_socket_timeout');
45     }
46
47     /**
48      * Connect (if necessary) and write to the socket
49      *
50      * @param array $record
51      *
52      * @throws \UnexpectedValueException
53      * @throws \RuntimeException
54      */
55     protected function write(array $record)
56     {
57         $this->connectIfNotConnected();
58         $data = $this->generateDataStream($record);
59         $this->writeToSocket($data);
60     }
61
62     /**
63      * We will not close a PersistentSocket instance so it can be reused in other requests.
64      */
65     public function close()
66     {
67         if (!$this->isPersistent()) {
68             $this->closeSocket();
69         }
70     }
71
72     /**
73      * Close socket, if open
74      */
75     public function closeSocket()
76     {
77         if (is_resource($this->resource)) {
78             fclose($this->resource);
79             $this->resource = null;
80         }
81     }
82
83     /**
84      * Set socket connection to nbe persistent. It only has effect before the connection is initiated.
85      *
86      * @param bool $persistent
87      */
88     public function setPersistent($persistent)
89     {
90         $this->persistent = (boolean) $persistent;
91     }
92
93     /**
94      * Set connection timeout.  Only has effect before we connect.
95      *
96      * @param float $seconds
97      *
98      * @see http://php.net/manual/en/function.fsockopen.php
99      */
100     public function setConnectionTimeout($seconds)
101     {
102         $this->validateTimeout($seconds);
103         $this->connectionTimeout = (float) $seconds;
104     }
105
106     /**
107      * Set write timeout. Only has effect before we connect.
108      *
109      * @param float $seconds
110      *
111      * @see http://php.net/manual/en/function.stream-set-timeout.php
112      */
113     public function setTimeout($seconds)
114     {
115         $this->validateTimeout($seconds);
116         $this->timeout = (float) $seconds;
117     }
118
119     /**
120      * Set writing timeout. Only has effect during connection in the writing cycle.
121      *
122      * @param float $seconds 0 for no timeout
123      */
124     public function setWritingTimeout($seconds)
125     {
126         $this->validateTimeout($seconds);
127         $this->writingTimeout = (float) $seconds;
128     }
129
130     /**
131      * Get current connection string
132      *
133      * @return string
134      */
135     public function getConnectionString()
136     {
137         return $this->connectionString;
138     }
139
140     /**
141      * Get persistent setting
142      *
143      * @return bool
144      */
145     public function isPersistent()
146     {
147         return $this->persistent;
148     }
149
150     /**
151      * Get current connection timeout setting
152      *
153      * @return float
154      */
155     public function getConnectionTimeout()
156     {
157         return $this->connectionTimeout;
158     }
159
160     /**
161      * Get current in-transfer timeout
162      *
163      * @return float
164      */
165     public function getTimeout()
166     {
167         return $this->timeout;
168     }
169
170     /**
171      * Get current local writing timeout
172      *
173      * @return float
174      */
175     public function getWritingTimeout()
176     {
177         return $this->writingTimeout;
178     }
179
180     /**
181      * Check to see if the socket is currently available.
182      *
183      * UDP might appear to be connected but might fail when writing.  See http://php.net/fsockopen for details.
184      *
185      * @return bool
186      */
187     public function isConnected()
188     {
189         return is_resource($this->resource)
190             && !feof($this->resource);  // on TCP - other party can close connection.
191     }
192
193     /**
194      * Wrapper to allow mocking
195      */
196     protected function pfsockopen()
197     {
198         return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout);
199     }
200
201     /**
202      * Wrapper to allow mocking
203      */
204     protected function fsockopen()
205     {
206         return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout);
207     }
208
209     /**
210      * Wrapper to allow mocking
211      *
212      * @see http://php.net/manual/en/function.stream-set-timeout.php
213      */
214     protected function streamSetTimeout()
215     {
216         $seconds = floor($this->timeout);
217         $microseconds = round(($this->timeout - $seconds) * 1e6);
218
219         return stream_set_timeout($this->resource, $seconds, $microseconds);
220     }
221
222     /**
223      * Wrapper to allow mocking
224      */
225     protected function fwrite($data)
226     {
227         return @fwrite($this->resource, $data);
228     }
229
230     /**
231      * Wrapper to allow mocking
232      */
233     protected function streamGetMetadata()
234     {
235         return stream_get_meta_data($this->resource);
236     }
237
238     private function validateTimeout($value)
239     {
240         $ok = filter_var($value, FILTER_VALIDATE_FLOAT);
241         if ($ok === false || $value < 0) {
242             throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)");
243         }
244     }
245
246     private function connectIfNotConnected()
247     {
248         if ($this->isConnected()) {
249             return;
250         }
251         $this->connect();
252     }
253
254     protected function generateDataStream($record)
255     {
256         return (string) $record['formatted'];
257     }
258
259     /**
260      * @return resource|null
261      */
262     protected function getResource()
263     {
264         return $this->resource;
265     }
266
267     private function connect()
268     {
269         $this->createSocketResource();
270         $this->setSocketTimeout();
271     }
272
273     private function createSocketResource()
274     {
275         if ($this->isPersistent()) {
276             $resource = $this->pfsockopen();
277         } else {
278             $resource = $this->fsockopen();
279         }
280         if (!$resource) {
281             throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)");
282         }
283         $this->resource = $resource;
284     }
285
286     private function setSocketTimeout()
287     {
288         if (!$this->streamSetTimeout()) {
289             throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()");
290         }
291     }
292
293     private function writeToSocket($data)
294     {
295         $length = strlen($data);
296         $sent = 0;
297         $this->lastSentBytes = $sent;
298         while ($this->isConnected() && $sent < $length) {
299             if (0 == $sent) {
300                 $chunk = $this->fwrite($data);
301             } else {
302                 $chunk = $this->fwrite(substr($data, $sent));
303             }
304             if ($chunk === false) {
305                 throw new \RuntimeException("Could not write to socket");
306             }
307             $sent += $chunk;
308             $socketInfo = $this->streamGetMetadata();
309             if ($socketInfo['timed_out']) {
310                 throw new \RuntimeException("Write timed-out");
311             }
312
313             if ($this->writingIsTimedOut($sent)) {
314                 throw new \RuntimeException("Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent $sent of $length)");
315             }
316         }
317         if (!$this->isConnected() && $sent < $length) {
318             throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)");
319         }
320     }
321
322     private function writingIsTimedOut($sent)
323     {
324         $writingTimeout = (int) floor($this->writingTimeout);
325         if (0 === $writingTimeout) {
326             return false;
327         }
328
329         if ($sent !== $this->lastSentBytes) {
330             $this->lastWritingAt = time();
331             $this->lastSentBytes = $sent;
332
333             return false;
334         } else {
335             usleep(100);
336         }
337
338         if ((time() - $this->lastWritingAt) >= $writingTimeout) {
339             $this->closeSocket();
340
341             return true;
342         }
343
344         return false;
345     }
346 }