Merge "Revert "Log the reason why revision->getContent() returns null""
[lhc/web/wiklou.git] / includes / debug / logger / MonologSpi.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 namespace MediaWiki\Logger;
22
23 use MediaWiki\Logger\Monolog\BufferHandler;
24 use Monolog\Logger;
25 use Wikimedia\ObjectFactory;
26
27 /**
28 * LoggerFactory service provider that creates loggers implemented by
29 * Monolog.
30 *
31 * Configured using an array of configuration data with the keys 'loggers',
32 * 'processors', 'handlers' and 'formatters'.
33 *
34 * The ['loggers']['\@default'] configuration will be used to create loggers
35 * for any channel that isn't explicitly named in the 'loggers' configuration
36 * section.
37 *
38 * Configuration will most typically be provided in the $wgMWLoggerDefaultSpi
39 * global configuration variable used by LoggerFactory to construct its
40 * default SPI provider:
41 * @code
42 * $wgMWLoggerDefaultSpi = [
43 * 'class' => \MediaWiki\Logger\MonologSpi::class,
44 * 'args' => [ [
45 * 'loggers' => [
46 * '@default' => [
47 * 'processors' => [ 'wiki', 'psr', 'pid', 'uid', 'web' ],
48 * 'handlers' => [ 'stream' ],
49 * ],
50 * 'runJobs' => [
51 * 'processors' => [ 'wiki', 'psr', 'pid' ],
52 * 'handlers' => [ 'stream' ],
53 * ]
54 * ],
55 * 'processors' => [
56 * 'wiki' => [
57 * 'class' => \MediaWiki\Logger\Monolog\WikiProcessor::class,
58 * ],
59 * 'psr' => [
60 * 'class' => \Monolog\Processor\PsrLogMessageProcessor::class,
61 * ],
62 * 'pid' => [
63 * 'class' => \Monolog\Processor\ProcessIdProcessor::class,
64 * ],
65 * 'uid' => [
66 * 'class' => \Monolog\Processor\UidProcessor::class,
67 * ],
68 * 'web' => [
69 * 'class' => \Monolog\Processor\WebProcessor::class,
70 * ],
71 * ],
72 * 'handlers' => [
73 * 'stream' => [
74 * 'class' => \Monolog\Handler\StreamHandler::class,
75 * 'args' => [ 'path/to/your.log' ],
76 * 'formatter' => 'line',
77 * ],
78 * 'redis' => [
79 * 'class' => \Monolog\Handler\RedisHandler::class,
80 * 'args' => [ function() {
81 * $redis = new Redis();
82 * $redis->connect( '127.0.0.1', 6379 );
83 * return $redis;
84 * },
85 * 'logstash'
86 * ],
87 * 'formatter' => 'logstash',
88 * 'buffer' => true,
89 * ],
90 * 'udp2log' => [
91 * 'class' => \MediaWiki\Logger\Monolog\LegacyHandler::class,
92 * 'args' => [
93 * 'udp://127.0.0.1:8420/mediawiki
94 * ],
95 * 'formatter' => 'line',
96 * ],
97 * ],
98 * 'formatters' => [
99 * 'line' => [
100 * 'class' => \Monolog\Formatter\LineFormatter::class,
101 * ],
102 * 'logstash' => [
103 * 'class' => \Monolog\Formatter\LogstashFormatter::class,
104 * 'args' => [ 'mediawiki', php_uname( 'n' ), null, '', 1 ],
105 * ],
106 * ],
107 * ] ],
108 * ];
109 * @endcode
110 *
111 * @see https://github.com/Seldaek/monolog
112 * @since 1.25
113 * @copyright © 2014 Wikimedia Foundation and contributors
114 */
115 class MonologSpi implements Spi {
116
117 /**
118 * @var array $singletons
119 */
120 protected $singletons;
121
122 /**
123 * Configuration for creating new loggers.
124 * @var array $config
125 */
126 protected $config;
127
128 /**
129 * @param array $config Configuration data.
130 */
131 public function __construct( array $config ) {
132 $this->config = [];
133 $this->mergeConfig( $config );
134 }
135
136 /**
137 * Merge additional configuration data into the configuration.
138 *
139 * @since 1.26
140 * @param array $config Configuration data.
141 */
142 public function mergeConfig( array $config ) {
143 foreach ( $config as $key => $value ) {
144 if ( isset( $this->config[$key] ) ) {
145 $this->config[$key] = array_merge( $this->config[$key], $value );
146 } else {
147 $this->config[$key] = $value;
148 }
149 }
150 $this->reset();
151 }
152
153 /**
154 * Reset internal caches.
155 *
156 * This is public for use in unit tests. Under normal operation there should
157 * be no need to flush the caches.
158 */
159 public function reset() {
160 $this->singletons = [
161 'loggers' => [],
162 'handlers' => [],
163 'formatters' => [],
164 'processors' => [],
165 ];
166 }
167
168 /**
169 * Get a logger instance.
170 *
171 * Creates and caches a logger instance based on configuration found in the
172 * $wgMWLoggerMonologSpiConfig global. Subsequent request for the same channel
173 * name will return the cached instance.
174 *
175 * @param string $channel Logging channel
176 * @return \Psr\Log\LoggerInterface Logger instance
177 */
178 public function getLogger( $channel ) {
179 if ( !isset( $this->singletons['loggers'][$channel] ) ) {
180 // Fallback to using the '@default' configuration if an explict
181 // configuration for the requested channel isn't found.
182 $spec = isset( $this->config['loggers'][$channel] ) ?
183 $this->config['loggers'][$channel] :
184 $this->config['loggers']['@default'];
185
186 $monolog = $this->createLogger( $channel, $spec );
187 $this->singletons['loggers'][$channel] = $monolog;
188 }
189
190 return $this->singletons['loggers'][$channel];
191 }
192
193 /**
194 * Create a logger.
195 * @param string $channel Logger channel
196 * @param array $spec Configuration
197 * @return \Monolog\Logger
198 */
199 protected function createLogger( $channel, $spec ) {
200 $obj = new Logger( $channel );
201
202 if ( isset( $spec['calls'] ) ) {
203 foreach ( $spec['calls'] as $method => $margs ) {
204 call_user_func_array( [ $obj, $method ], $margs );
205 }
206 }
207
208 if ( isset( $spec['processors'] ) ) {
209 foreach ( $spec['processors'] as $processor ) {
210 $obj->pushProcessor( $this->getProcessor( $processor ) );
211 }
212 }
213
214 if ( isset( $spec['handlers'] ) ) {
215 foreach ( $spec['handlers'] as $handler ) {
216 $obj->pushHandler( $this->getHandler( $handler ) );
217 }
218 }
219 return $obj;
220 }
221
222 /**
223 * Create or return cached processor.
224 * @param string $name Processor name
225 * @return callable
226 */
227 public function getProcessor( $name ) {
228 if ( !isset( $this->singletons['processors'][$name] ) ) {
229 $spec = $this->config['processors'][$name];
230 $processor = ObjectFactory::getObjectFromSpec( $spec );
231 $this->singletons['processors'][$name] = $processor;
232 }
233 return $this->singletons['processors'][$name];
234 }
235
236 /**
237 * Create or return cached handler.
238 * @param string $name Processor name
239 * @return \Monolog\Handler\HandlerInterface
240 */
241 public function getHandler( $name ) {
242 if ( !isset( $this->singletons['handlers'][$name] ) ) {
243 $spec = $this->config['handlers'][$name];
244 $handler = ObjectFactory::getObjectFromSpec( $spec );
245 if ( isset( $spec['formatter'] ) ) {
246 $handler->setFormatter(
247 $this->getFormatter( $spec['formatter'] )
248 );
249 }
250 if ( isset( $spec['buffer'] ) && $spec['buffer'] ) {
251 $handler = new BufferHandler( $handler );
252 }
253 $this->singletons['handlers'][$name] = $handler;
254 }
255 return $this->singletons['handlers'][$name];
256 }
257
258 /**
259 * Create or return cached formatter.
260 * @param string $name Formatter name
261 * @return \Monolog\Formatter\FormatterInterface
262 */
263 public function getFormatter( $name ) {
264 if ( !isset( $this->singletons['formatters'][$name] ) ) {
265 $spec = $this->config['formatters'][$name];
266 $formatter = ObjectFactory::getObjectFromSpec( $spec );
267 $this->singletons['formatters'][$name] = $formatter;
268 }
269 return $this->singletons['formatters'][$name];
270 }
271 }