Add a PSR-3 based logging interface
[lhc/web/wiklou.git] / includes / debug / logger / monolog / Handler.php
1 <?php
2 /**
3 * @section LICENSE
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 * http://www.gnu.org/copyleft/gpl.html
18 *
19 * @file
20 */
21
22
23 /**
24 * Log handler that replicates the behavior of MediaWiki's wfErrorLog()
25 * logging service. Log output can be directed to a local file, a PHP stream,
26 * or a udp2log server.
27 *
28 * For udp2log output, the stream specification must have the form:
29 * "udp://HOST:PORT[/PREFIX]"
30 * where:
31 * - HOST: IPv4, IPv6 or hostname
32 * - PORT: server port
33 * - PREFIX: optional (but recommended) prefix telling udp2log how to route
34 * the log event
35 *
36 * When not targeting a udp2log stream this class will act as a drop-in
37 * replacement for Monolog's StreamHandler.
38 *
39 * @since 1.25
40 * @author Bryan Davis <bd808@wikimedia.org>
41 * @copyright © 2013 Bryan Davis and Wikimedia Foundation.
42 */
43 class MWLoggerMonologHandler extends \Monolog\Handler\AbstractProcessingHandler {
44
45 /**
46 * Log sink descriptor
47 * @var string $uri
48 */
49 protected $uri;
50
51 /**
52 * Log sink
53 * @var resource $sink
54 */
55 protected $sink;
56
57 /**
58 * @var string $error
59 */
60 protected $error;
61
62 /**
63 * @var string $host
64 */
65 protected $host;
66
67 /**
68 * @var int $port
69 */
70 protected $port;
71
72 /**
73 * @var string $prefix
74 */
75 protected $prefix;
76
77
78 /**
79 * @param string $stream Stream URI
80 * @param int $level Minimum logging level that will trigger handler
81 * @param bool $bubble Can handled meesages bubble up the handler stack?
82 */
83 public function __construct(
84 $stream, $level = \Monolog\Logger::DEBUG, $bubble = true
85 ) {
86 parent::__construct( $level, $bubble );
87 $this->uri = $stream;
88 }
89
90
91 /**
92 * Open the log sink described by our stream URI.
93 */
94 protected function openSink() {
95 if ( !$this->uri ) {
96 throw new LogicException(
97 'Missing stream uri, the stream can not be opened.' );
98 }
99 $this->error = null;
100 set_error_handler( array( $this, 'errorTrap' ) );
101
102 if ( substr( $this->uri, 0, 4 ) == 'udp:' ) {
103 $parsed = parse_url( $this->uri );
104 if ( !isset( $parsed['host'] ) ) {
105 throw new UnexpectedValueException( sprintf(
106 'Udp transport "%s" must specify a host', $this->uri
107 ) );
108 }
109 if ( !isset( $parsed['port'] ) ) {
110 throw new UnexpectedValueException( sprintf(
111 'Udp transport "%s" must specify a port', $this->uri
112 ) );
113 }
114
115 $this->host = $parsed['host'];
116 $this->port = $parsed['port'];
117 $this->prefix = '';
118
119 if ( isset( $parsed['path'] ) ) {
120 $this->prefix = ltrim( $parsed['path'], '/' );
121 }
122
123 if ( filter_var( $this->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
124 $domain = AF_INET6;
125
126 } else {
127 $domain = AF_INET;
128 }
129
130 $this->sink = socket_create( $domain, SOCK_DGRAM, SOL_UDP );
131
132 } else {
133 $this->sink = fopen( $this->uri, 'a' );
134 }
135 restore_error_handler();
136
137 if ( !is_resource( $this->sink ) ) {
138 $this->sink = null;
139 throw new UnexpectedValueException( sprintf(
140 'The stream or file "%s" could not be opened: %s',
141 $this->uri, $this->error
142 ) );
143 }
144 }
145
146
147 /**
148 * Custom error handler.
149 * @param int $code Error number
150 * @param string $msg Error message
151 */
152 protected function errorTrap( $code, $msg ) {
153 $this->error = $msg;
154 }
155
156
157 /**
158 * Should we use UDP to send messages to the sink?
159 * @return bool
160 */
161 protected function useUdp() {
162 return $this->host !== null;
163 }
164
165
166 protected function write( array $record ) {
167 if ( $this->sink === null ) {
168 $this->openSink();
169 }
170
171 $text = (string) $record['formatted'];
172 if ( $this->useUdp() ) {
173
174 // Clean it up for the multiplexer
175 if ( $this->prefix !== '' ) {
176 $text = preg_replace( '/^/m', "{$this->prefix} ", $text );
177
178 // Limit to 64KB
179 if ( strlen( $text ) > 65506 ) {
180 $text = substr( $text, 0, 65506 );
181 }
182
183 if ( substr( $text, -1 ) != "\n" ) {
184 $text .= "\n";
185 }
186
187 } elseif ( strlen( $text ) > 65507 ) {
188 $text = substr( $text, 0, 65507 );
189 }
190
191 socket_sendto(
192 $this->sink, $text, strlen( $text ), 0, $this->host, $this->port );
193
194 } else {
195 fwrite( $this->sink, $text );
196 }
197 }
198
199
200 public function close() {
201 if ( is_resource( $this->sink ) ) {
202 if ( $this->useUdp() ) {
203 socket_close( $this->sink );
204
205 } else {
206 fclose( $this->sink );
207 }
208 }
209 $this->sink = null;
210 }
211
212 }