Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / debug / logger / MonologSpi.php
blob5c175e339bcc489828f969312a0b0fb2cffa7ef3
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.
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
18 * @file
21 namespace MediaWiki\Logger;
23 use DateTimeZone;
24 use MediaWiki\Logger\Monolog\BufferHandler;
25 use Monolog\Formatter\FormatterInterface;
26 use Monolog\Handler\FormattableHandlerInterface;
27 use Monolog\Handler\HandlerInterface;
28 use Monolog\Handler\PsrHandler;
29 use Monolog\Handler\StreamHandler;
30 use Monolog\Logger;
31 use Psr\Log\LoggerInterface;
32 use Wikimedia\ObjectFactory\ObjectFactory;
34 /**
35 * LoggerFactory service provider that creates loggers implemented by
36 * Monolog.
38 * Configured using an array of configuration data with the keys 'loggers',
39 * 'processors', 'handlers' and 'formatters'.
41 * The ['loggers']['\@default'] configuration will be used to create loggers
42 * for any channel that isn't explicitly named in the 'loggers' configuration
43 * section.
45 * Configuration will most typically be provided in the $wgMWLoggerDefaultSpi
46 * global configuration variable used by LoggerFactory to construct its
47 * default SPI provider:
48 * @code
49 * $wgMWLoggerDefaultSpi = [
50 * 'class' => \MediaWiki\Logger\MonologSpi::class,
51 * 'args' => [ [
52 * 'loggers' => [
53 * '@default' => [
54 * 'processors' => [ 'wiki', 'psr', 'pid', 'uid', 'web' ],
55 * 'handlers' => [ 'stream' ],
56 * ],
57 * 'runJobs' => [
58 * 'processors' => [ 'wiki', 'psr', 'pid' ],
59 * 'handlers' => [ 'stream' ],
60 * ]
61 * ],
62 * 'processors' => [
63 * 'wiki' => [
64 * 'class' => \MediaWiki\Logger\Monolog\WikiProcessor::class,
65 * ],
66 * 'psr' => [
67 * 'class' => \Monolog\Processor\PsrLogMessageProcessor::class,
68 * ],
69 * 'pid' => [
70 * 'class' => \Monolog\Processor\ProcessIdProcessor::class,
71 * ],
72 * 'uid' => [
73 * 'class' => \Monolog\Processor\UidProcessor::class,
74 * ],
75 * 'web' => [
76 * 'class' => \Monolog\Processor\WebProcessor::class,
77 * ],
78 * ],
79 * 'handlers' => [
80 * 'stream' => [
81 * 'class' => \Monolog\Handler\StreamHandler::class,
82 * 'args' => [ 'path/to/your.log' ],
83 * 'formatter' => 'line',
84 * ],
85 * 'redis' => [
86 * 'class' => \Monolog\Handler\RedisHandler::class,
87 * 'args' => [ function() {
88 * $redis = new Redis();
89 * $redis->connect( '127.0.0.1', 6379 );
90 * return $redis;
91 * },
92 * 'logstash'
93 * ],
94 * 'formatter' => 'logstash',
95 * 'buffer' => true,
96 * ],
97 * 'udp2log' => [
98 * 'class' => \MediaWiki\Logger\Monolog\LegacyHandler::class,
99 * 'args' => [
100 * 'udp://127.0.0.1:8420/mediawiki
101 * ],
102 * 'formatter' => 'line',
103 * ],
104 * ],
105 * 'formatters' => [
106 * 'line' => [
107 * 'class' => \Monolog\Formatter\LineFormatter::class,
108 * ],
109 * 'logstash' => [
110 * 'class' => \Monolog\Formatter\LogstashFormatter::class,
111 * 'args' => [ 'mediawiki', php_uname( 'n' ), null, '', 1 ],
112 * ],
113 * ],
114 * ] ],
115 * ];
116 * @endcode
118 * @see https://github.com/Seldaek/monolog
119 * @since 1.25
120 * @ingroup Debug
121 * @copyright © 2014 Wikimedia Foundation and contributors
123 class MonologSpi implements Spi {
126 * @var array{loggers:LoggerInterface[],handlers:HandlerInterface[],formatters:FormatterInterface[],processors:callable[]}
128 protected $singletons;
131 * Configuration for creating new loggers.
132 * @var array<string,array<string,array>>
134 protected array $config = [];
137 * @param array $config Configuration data.
139 public function __construct( array $config ) {
140 $this->mergeConfig( $config );
144 * Merge additional configuration data into the configuration.
146 * @since 1.26
147 * @param array $config Configuration data.
149 public function mergeConfig( array $config ) {
150 foreach ( $config as $key => $value ) {
151 if ( isset( $this->config[$key] ) ) {
152 $this->config[$key] = array_merge( $this->config[$key], $value );
153 } else {
154 $this->config[$key] = $value;
157 if ( !isset( $this->config['loggers']['@default'] ) ) {
158 $this->config['loggers']['@default'] = [
159 'handlers' => [ '@default' ],
161 $this->config['handlers']['@default'] ??= [
162 'class' => StreamHandler::class,
163 'args' => [ 'php://stderr', Logger::ERROR ],
166 $this->reset();
170 * Reset internal caches.
172 * This is public for use in unit tests. Under normal operation there should
173 * be no need to flush the caches.
175 public function reset() {
176 $this->singletons = [
177 'loggers' => [],
178 'handlers' => [],
179 'formatters' => [],
180 'processors' => [],
185 * Get a logger instance.
187 * Creates and caches a logger instance based on configuration found in the
188 * $wgMWLoggerMonologSpiConfig global. Subsequent request for the same channel
189 * name will return the cached instance.
191 * @param string $channel Logging channel
192 * @return LoggerInterface
194 public function getLogger( $channel ) {
195 if ( !isset( $this->singletons['loggers'][$channel] ) ) {
196 // Fallback to using the '@default' configuration if an explicit
197 // configuration for the requested channel isn't found.
198 $spec = $this->config['loggers'][$channel] ?? $this->config['loggers']['@default'];
200 $monolog = $this->createLogger( $channel, $spec );
201 $this->singletons['loggers'][$channel] = $monolog;
204 return $this->singletons['loggers'][$channel];
208 * Create a logger.
209 * @param string $channel Logger channel
210 * @param array $spec Configuration
211 * @return LoggerInterface
213 protected function createLogger( $channel, $spec ): LoggerInterface {
214 global $wgShowDebug, $wgDebugToolbar;
216 $handlers = [];
217 if ( isset( $spec['handlers'] ) && $spec['handlers'] ) {
218 foreach ( $spec['handlers'] as $handler ) {
219 $handlers[] = $this->getHandler( $handler );
223 $processors = [];
224 if ( isset( $spec['processors'] ) ) {
225 foreach ( $spec['processors'] as $processor ) {
226 $processors[] = $this->getProcessor( $processor );
230 // Use UTC for logs instead of Monolog's default, which asks the
231 // PHP runtime, which MediaWiki sets to $wgLocaltimezone (T99581)
232 $obj = new Logger( $channel, $handlers, $processors, new DateTimeZone( 'UTC' ) );
234 if ( $wgShowDebug || $wgDebugToolbar ) {
235 $legacyLogger = new LegacyLogger( $channel );
236 $legacyPsrHandler = new PsrHandler( $legacyLogger );
237 $obj->pushHandler( $legacyPsrHandler );
240 if ( isset( $spec['calls'] ) ) {
241 foreach ( $spec['calls'] as $method => $margs ) {
242 $obj->$method( ...$margs );
246 return $obj;
250 * Create or return cached processor.
251 * @param string $name Processor name
252 * @return callable
254 public function getProcessor( $name ) {
255 if ( !isset( $this->singletons['processors'][$name] ) ) {
256 $spec = $this->config['processors'][$name];
257 /** @var callable $processor */
258 $processor = ObjectFactory::getObjectFromSpec( $spec );
259 $this->singletons['processors'][$name] = $processor;
261 return $this->singletons['processors'][$name];
265 * Create or return cached handler.
266 * @param string $name Processor name
267 * @return HandlerInterface
269 public function getHandler( $name ) {
270 if ( !isset( $this->singletons['handlers'][$name] ) ) {
271 $spec = $this->config['handlers'][$name];
272 /** @var HandlerInterface $handler */
273 $handler = ObjectFactory::getObjectFromSpec( $spec );
274 if (
275 isset( $spec['formatter'] ) &&
276 $handler instanceof FormattableHandlerInterface
278 $handler->setFormatter(
279 $this->getFormatter( $spec['formatter'] )
282 if ( isset( $spec['buffer'] ) && $spec['buffer'] ) {
283 $handler = new BufferHandler( $handler );
285 $this->singletons['handlers'][$name] = $handler;
287 return $this->singletons['handlers'][$name];
291 * Create or return cached formatter.
292 * @param string $name Formatter name
293 * @return FormatterInterface
295 public function getFormatter( $name ) {
296 if ( !isset( $this->singletons['formatters'][$name] ) ) {
297 $spec = $this->config['formatters'][$name];
298 /** @var FormatterInterface $formatter */
299 $formatter = ObjectFactory::getObjectFromSpec( $spec );
300 $this->singletons['formatters'][$name] = $formatter;
302 return $this->singletons['formatters'][$name];