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