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