scratch – Blame information for rev 115

Subversion Repositories:
Rev:
Rev Author Line No. Line
115 office 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 }