Remove '@section LICENSE'
[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
34 *
35 * When not targeting a udp2log stream this class will act as a drop-in
36 * replacement for Monolog's StreamHandler.
37 *
38 * @since 1.25
39 * @author Bryan Davis <bd808@wikimedia.org>
40 * @copyright © 2013 Bryan Davis and Wikimedia Foundation.
41 */
42 class MWLoggerMonologHandler extends \Monolog\Handler\AbstractProcessingHandler {
43
44 /**
45 * Log sink descriptor
46 * @var string $uri
47 */
48 protected $uri;
49
50 /**
51 * Filter log events using legacy rules
52 * @var bool $useLegacyFilter
53 */
54 protected $useLegacyFilter;
55
56 /**
57 * Log sink
58 * @var resource $sink
59 */
60 protected $sink;
61
62 /**
63 * @var string $error
64 */
65 protected $error;
66
67 /**
68 * @var string $host
69 */
70 protected $host;
71
72 /**
73 * @var int $port
74 */
75 protected $port;
76
77 /**
78 * @var string $prefix
79 */
80 protected $prefix;
81
82
83 /**
84 * @param string $stream Stream URI
85 * @param bool $useLegacyFilter Filter log events using legacy rules
86 * @param int $level Minimum logging level that will trigger handler
87 * @param bool $bubble Can handled meesages bubble up the handler stack?
88 */
89 public function __construct(
90 $stream,
91 $useLegacyFilter = false,
92 $level = \Monolog\Logger::DEBUG,
93 $bubble = true
94 ) {
95 parent::__construct( $level, $bubble );
96 $this->uri = $stream;
97 $this->useLegacyFilter = $useLegacyFilter;
98 }
99
100 public function isHandling( array $record ) {
101 $levelOk = parent::isHandling( $record );
102 if ( $levelOk && $this->useLegacyFilter ) {
103 return MWLoggerLegacyLogger::shouldEmit(
104 $record['channel'], $record['message'], $record
105 );
106 }
107 return $levelOk;
108 }
109
110 /**
111 * Open the log sink described by our stream URI.
112 */
113 protected function openSink() {
114 if ( !$this->uri ) {
115 throw new LogicException(
116 'Missing stream uri, the stream can not be opened.' );
117 }
118 $this->error = null;
119 set_error_handler( array( $this, 'errorTrap' ) );
120
121 if ( substr( $this->uri, 0, 4 ) == 'udp:' ) {
122 $parsed = parse_url( $this->uri );
123 if ( !isset( $parsed['host'] ) ) {
124 throw new UnexpectedValueException( sprintf(
125 'Udp transport "%s" must specify a host', $this->uri
126 ) );
127 }
128 if ( !isset( $parsed['port'] ) ) {
129 throw new UnexpectedValueException( sprintf(
130 'Udp transport "%s" must specify a port', $this->uri
131 ) );
132 }
133
134 $this->host = $parsed['host'];
135 $this->port = $parsed['port'];
136 $this->prefix = '';
137
138 if ( isset( $parsed['path'] ) ) {
139 $this->prefix = ltrim( $parsed['path'], '/' );
140 }
141
142 if ( filter_var( $this->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
143 $domain = AF_INET6;
144
145 } else {
146 $domain = AF_INET;
147 }
148
149 $this->sink = socket_create( $domain, SOCK_DGRAM, SOL_UDP );
150
151 } else {
152 $this->sink = fopen( $this->uri, 'a' );
153 }
154 restore_error_handler();
155
156 if ( !is_resource( $this->sink ) ) {
157 $this->sink = null;
158 throw new UnexpectedValueException( sprintf(
159 'The stream or file "%s" could not be opened: %s',
160 $this->uri, $this->error
161 ) );
162 }
163 }
164
165
166 /**
167 * Custom error handler.
168 * @param int $code Error number
169 * @param string $msg Error message
170 */
171 protected function errorTrap( $code, $msg ) {
172 $this->error = $msg;
173 }
174
175
176 /**
177 * Should we use UDP to send messages to the sink?
178 * @return bool
179 */
180 protected function useUdp() {
181 return $this->host !== null;
182 }
183
184
185 protected function write( array $record ) {
186 if ( $this->sink === null ) {
187 $this->openSink();
188 }
189
190 $text = (string)$record['formatted'];
191 if ( $this->useUdp() ) {
192
193 // Clean it up for the multiplexer
194 if ( $this->prefix !== '' ) {
195 $text = preg_replace( '/^/m', "{$this->prefix} ", $text );
196
197 // Limit to 64KB
198 if ( strlen( $text ) > 65506 ) {
199 $text = substr( $text, 0, 65506 );
200 }
201
202 if ( substr( $text, -1 ) != "\n" ) {
203 $text .= "\n";
204 }
205
206 } elseif ( strlen( $text ) > 65507 ) {
207 $text = substr( $text, 0, 65507 );
208 }
209
210 socket_sendto(
211 $this->sink, $text, strlen( $text ), 0, $this->host, $this->port );
212
213 } else {
214 fwrite( $this->sink, $text );
215 }
216 }
217
218
219 public function close() {
220 if ( is_resource( $this->sink ) ) {
221 if ( $this->useUdp() ) {
222 socket_close( $this->sink );
223
224 } else {
225 fclose( $this->sink );
226 }
227 }
228 $this->sink = null;
229 }
230
231 }