1<?php
2
3namespace MediaWiki\Logger;
4
5use Psr\Log\AbstractLogger;
6use Psr\Log\LoggerInterface;
7
8/**
9 * Wraps another spi to capture all logs generated. This can be
10 * used, for example, to collect all logs generated during a
11 * unit test and report them when the test fails.
12 */
13class LogCapturingSpi implements Spi {
14	/** @var LoggerInterface[] */
15	private $singletons;
16	/** @var Spi */
17	private $inner;
18	/** @var array */
19	private $logs = [];
20
21	public function __construct( Spi $inner ) {
22		$this->inner = $inner;
23	}
24
25	/**
26	 * @return array
27	 */
28	public function getLogs() {
29		return $this->logs;
30	}
31
32	/**
33	 * @param string $channel
34	 * @return LoggerInterface
35	 */
36	public function getLogger( $channel ) {
37		if ( !isset( $this->singletons[$channel] ) ) {
38			$this->singletons[$channel] = $this->createLogger( $channel );
39		}
40		return $this->singletons[$channel];
41	}
42
43	/**
44	 * @param array $log
45	 */
46	public function capture( $log ) {
47		$this->logs[] = $log;
48	}
49
50	/**
51	 * @param string $channel
52	 * @return LoggerInterface
53	 */
54	private function createLogger( $channel ) {
55		$inner = $this->inner->getLogger( $channel );
56		return new class( $channel, $inner, $this ) extends AbstractLogger {
57			/** @var string */
58			private $channel;
59			/** @var LoggerInterface */
60			private $logger;
61			/** @var LogCapturingSpi */
62			private $parent;
63
64			// phpcs:ignore MediaWiki.Usage.NestedFunctions.NestedFunction
65			public function __construct( $channel, LoggerInterface $logger, LogCapturingSpi $parent ) {
66				$this->channel = $channel;
67				$this->logger = $logger;
68				$this->parent = $parent;
69			}
70
71			// phpcs:ignore MediaWiki.Usage.NestedFunctions.NestedFunction
72			public function log( $level, $message, array $context = [] ) {
73				$this->parent->capture( [
74					'channel' => $this->channel,
75					'level' => $level,
76					'message' => $message,
77					'context' => $context
78				] );
79				$this->logger->log( $level, $message, $context );
80			}
81		};
82	}
83
84	/**
85	 * @internal For use by MediaWikiIntegrationTestCase
86	 * @return Spi
87	 */
88	public function getInnerSpi() : Spi {
89		return $this->inner;
90	}
91
92	/**
93	 * @internal For use by MediaWikiIntegrationTestCase
94	 * @param string $channel
95	 * @param LoggerInterface|null $logger
96	 * @return LoggerInterface|null
97	 */
98	public function setLoggerForTest( $channel, LoggerInterface $logger = null ) {
99		$ret = $this->singletons[$channel] ?? null;
100		$this->singletons[$channel] = $logger;
101		return $ret;
102	}
103}
104