MonologSpi.php 8.3 KB

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