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			public function __construct( $channel, LoggerInterface $logger, LogCapturingSpi $parent ) {
65				$this->channel = $channel;
66				$this->logger = $logger;
67				$this->parent = $parent;
68			}
69
70			public function log( $level, $message, array $context = [] ) {
71				$this->parent->capture( [
72					'channel' => $this->channel,
73					'level' => $level,
74					'message' => $message,
75					'context' => $context
76				] );
77				$this->logger->log( $level, $message, $context );
78			}
79		};
80	}
81
82	/**
83	 * @internal For use by MediaWikiIntegrationTestCase
84	 * @return Spi
85	 */
86	public function getInnerSpi(): Spi {
87		return $this->inner;
88	}
89
90	/**
91	 * @internal For use by MediaWikiIntegrationTestCase
92	 * @param string $channel
93	 * @param LoggerInterface|null $logger
94	 * @return LoggerInterface|null
95	 */
96	public function setLoggerForTest( $channel, LoggerInterface $logger = null ) {
97		$ret = $this->singletons[$channel] ?? null;
98		$this->singletons[$channel] = $logger;
99		return $ret;
100	}
101}
102