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.
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.
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
21 namespace MediaWiki\Logger\Monolog
;
24 use MediaWiki\Logger\LegacyLogger
;
25 use Monolog\Handler\AbstractProcessingHandler
;
27 use UnexpectedValueException
;
30 * Log handler that replicates the behavior of MediaWiki's wfErrorLog()
31 * logging service. Log output can be directed to a local file, a PHP stream,
32 * or a udp2log server.
34 * For udp2log output, the stream specification must have the form:
35 * "udp://HOST:PORT[/PREFIX]"
37 * - HOST: IPv4, IPv6 or hostname
39 * - PREFIX: optional (but recommended) prefix telling udp2log how to route
40 * the log event. The special prefix "{channel}" will use the log event's
41 * channel as the prefix value.
43 * When not targeting a udp2log stream this class will act as a drop-in
44 * replacement for \Monolog\Handler\StreamHandler.
47 * @author Bryan Davis <bd808@wikimedia.org>
48 * @copyright © 2013 Bryan Davis and Wikimedia Foundation.
50 class LegacyHandler
extends AbstractProcessingHandler
{
59 * Filter log events using legacy rules
60 * @var bool $useLegacyFilter
62 protected $useLegacyFilter;
91 * @param string $stream Stream URI
92 * @param bool $useLegacyFilter Filter log events using legacy rules
93 * @param int $level Minimum logging level that will trigger handler
94 * @param bool $bubble Can handled meesages bubble up the handler stack?
96 public function __construct(
98 $useLegacyFilter = false,
99 $level = Logger
::DEBUG
,
102 parent
::__construct( $level, $bubble );
103 $this->uri
= $stream;
104 $this->useLegacyFilter
= $useLegacyFilter;
108 * Open the log sink described by our stream URI.
110 protected function openSink() {
112 throw new LogicException(
113 'Missing stream uri, the stream can not be opened.' );
116 set_error_handler( [ $this, 'errorTrap' ] );
118 if ( substr( $this->uri
, 0, 4 ) == 'udp:' ) {
119 $parsed = parse_url( $this->uri
);
120 if ( !isset( $parsed['host'] ) ) {
121 throw new UnexpectedValueException( sprintf(
122 'Udp transport "%s" must specify a host', $this->uri
125 if ( !isset( $parsed['port'] ) ) {
126 throw new UnexpectedValueException( sprintf(
127 'Udp transport "%s" must specify a port', $this->uri
131 $this->host
= $parsed['host'];
132 $this->port
= $parsed['port'];
135 if ( isset( $parsed['path'] ) ) {
136 $this->prefix
= ltrim( $parsed['path'], '/' );
139 if ( filter_var( $this->host
, FILTER_VALIDATE_IP
, FILTER_FLAG_IPV6
) ) {
146 $this->sink
= socket_create( $domain, SOCK_DGRAM
, SOL_UDP
);
149 $this->sink
= fopen( $this->uri
, 'a' );
151 restore_error_handler();
153 if ( !is_resource( $this->sink
) ) {
155 throw new UnexpectedValueException( sprintf(
156 'The stream or file "%s" could not be opened: %s',
157 $this->uri
, $this->error
163 * Custom error handler.
164 * @param int $code Error number
165 * @param string $msg Error message
167 protected function errorTrap( $code, $msg ) {
172 * Should we use UDP to send messages to the sink?
175 protected function useUdp() {
176 return $this->host
!== null;
179 protected function write( array $record ) {
180 if ( $this->useLegacyFilter
&&
181 !LegacyLogger
::shouldEmit(
182 $record['channel'], $record['message'],
183 $record['level'], $record
185 // Do not write record if we are enforcing legacy rules and they
186 // do not pass this message. This used to be done in isHandling(),
187 // but Monolog 1.12.0 made a breaking change that removed access
188 // to the needed channel and context information.
192 if ( $this->sink
=== null ) {
196 $text = (string)$record['formatted'];
197 if ( $this->useUdp() ) {
199 // Clean it up for the multiplexer
200 if ( $this->prefix
!== '' ) {
201 $leader = ( $this->prefix
=== '{channel}' ) ?
202 $record['channel'] : $this->prefix
;
203 $text = preg_replace( '/^/m', "{$leader} ", $text );
206 if ( strlen( $text ) > 65506 ) {
207 $text = substr( $text, 0, 65506 );
210 if ( substr( $text, -1 ) != "\n" ) {
214 } elseif ( strlen( $text ) > 65507 ) {
215 $text = substr( $text, 0, 65507 );
219 $this->sink
, $text, strlen( $text ), 0, $this->host
, $this->port
223 fwrite( $this->sink
, $text );
227 public function close() {
228 if ( is_resource( $this->sink
) ) {
229 if ( $this->useUdp() ) {
230 socket_close( $this->sink
);
233 fclose( $this->sink
);