[!!!][FEATURE] Introduce PSR-3 Logging
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Log / LogManager.php
1 <?php
2 namespace TYPO3\CMS\Core\Log;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\SingletonInterface;
18
19 /**
20 * Global LogManager that keeps track of global logging information.
21 *
22 * Inspired by java.util.logging
23 */
24 class LogManager implements SingletonInterface, LogManagerInterface
25 {
26 /**
27 * @var string
28 */
29 const CONFIGURATION_TYPE_WRITER = 'writer';
30
31 /**
32 * @var string
33 */
34 const CONFIGURATION_TYPE_PROCESSOR = 'processor';
35
36 /**
37 * Loggers to retrieve them for repeated use.
38 *
39 * @var array
40 */
41 protected $loggers = [];
42
43 /**
44 * Default / global / root logger.
45 *
46 * @var \TYPO3\CMS\Core\Log\Logger
47 */
48 protected $rootLogger;
49
50 /**
51 * Unique ID of the request
52 *
53 * @var string
54 */
55 protected $requestId = '';
56
57 /**
58 * Constructor
59 *
60 * @param string $requestId Unique ID of the request
61 */
62 public function __construct(string $requestId = '')
63 {
64 $this->requestId = $requestId;
65 $this->rootLogger = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(Logger::class, '', $requestId);
66 $this->loggers[''] = $this->rootLogger;
67 }
68
69 /**
70 * For use in unit test context only. Resets the internal logger registry.
71 */
72 public function reset()
73 {
74 $this->loggers = [];
75 }
76
77 /**
78 * Gets a logger instance for the given name.
79 *
80 * \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Log\LogManager::class)->getLogger('main.sub.subsub');
81 *
82 * $name can also be submitted as a underscore-separated string, which will
83 * be converted to dots. This is useful to call this method with __CLASS__
84 * as parameter.
85 *
86 * @param string $name Logger name, empty to get the global "root" logger.
87 * @return \TYPO3\CMS\Core\Log\Logger Logger with name $name
88 */
89 public function getLogger($name = '')
90 {
91 /** @var \TYPO3\CMS\Core\Log\Logger $logger */
92 $logger = null;
93 // Transform namespaces and underscore class names to the dot-name style
94 $separators = ['_', '\\'];
95 $name = str_replace($separators, '.', $name);
96 if (isset($this->loggers[$name])) {
97 $logger = $this->loggers[$name];
98 } else {
99 // Lazy instantiation
100 /** @var \TYPO3\CMS\Core\Log\Logger $logger */
101 $logger = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(Logger::class, $name, $this->requestId);
102 $this->loggers[$name] = $logger;
103 $this->setWritersForLogger($logger);
104 $this->setProcessorsForLogger($logger);
105 }
106 return $logger;
107 }
108
109 /**
110 * For use in unit test context only.
111 *
112 * @param string $name
113 */
114 public function registerLogger($name)
115 {
116 $this->loggers[$name] = null;
117 }
118
119 /**
120 * For use in unit test context only.
121 *
122 * @return array
123 */
124 public function getLoggerNames()
125 {
126 return array_keys($this->loggers);
127 }
128
129 /**
130 * Appends the writers to the given logger as configured.
131 *
132 * @param \TYPO3\CMS\Core\Log\Logger $logger Logger to configure
133 */
134 protected function setWritersForLogger(Logger $logger)
135 {
136 $configuration = $this->getConfigurationForLogger(self::CONFIGURATION_TYPE_WRITER, $logger->getName());
137 foreach ($configuration as $severityLevel => $writer) {
138 foreach ($writer as $logWriterClassName => $logWriterOptions) {
139 try {
140 /** @var \TYPO3\CMS\Core\Log\Writer\WriterInterface $logWriter */
141 $logWriter = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance($logWriterClassName, $logWriterOptions);
142 $logger->addWriter($severityLevel, $logWriter);
143 } catch (\Psr\Log\InvalidArgumentException $e) {
144 $logger->warning('Instantiation of LogWriter "' . $logWriterClassName . '" failed for logger ' . $logger->getName() . ' (' . $e->getMessage() . ')');
145 } catch (\TYPO3\CMS\Core\Log\Exception\InvalidLogWriterConfigurationException $e) {
146 $logger->warning('Instantiation of LogWriter "' . $logWriterClassName . '" failed for logger ' . $logger->getName() . ' (' . $e->getMessage() . ')');
147 }
148 }
149 }
150 }
151
152 /**
153 * Appends the processors to the given logger as configured.
154 *
155 * @param \TYPO3\CMS\Core\Log\Logger $logger Logger to configure
156 */
157 protected function setProcessorsForLogger(Logger $logger)
158 {
159 $configuration = $this->getConfigurationForLogger(self::CONFIGURATION_TYPE_PROCESSOR, $logger->getName());
160 foreach ($configuration as $severityLevel => $processor) {
161 foreach ($processor as $logProcessorClassName => $logProcessorOptions) {
162 try {
163 /** @var \TYPO3\CMS\Core\Log\Processor\ProcessorInterface $logProcessor */
164 $logProcessor = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance($logProcessorClassName, $logProcessorOptions);
165 $logger->addProcessor($severityLevel, $logProcessor);
166 } catch (\Psr\Log\InvalidArgumentException $e) {
167 $logger->warning('Instantiation of LogProcessor "' . $logProcessorClassName . '" failed for logger ' . $logger->getName() . ' (' . $e->getMessage() . ')');
168 } catch (\TYPO3\CMS\Core\Log\Exception\InvalidLogProcessorConfigurationException $e) {
169 $logger->warning('Instantiation of LogProcessor "' . $logProcessorClassName . '" failed for logger ' . $logger->getName() . ' (' . $e->getMessage() . ')');
170 }
171 }
172 }
173 }
174
175 /**
176 * Returns the configuration from $TYPO3_CONF_VARS['LOG'] as
177 * hierarchical array for different components of the class hierarchy.
178 *
179 * @param string $configurationType Type of config to return (writer, processor)
180 * @param string $loggerName Logger name
181 * @throws \Psr\Log\InvalidArgumentException
182 * @return array
183 */
184 protected function getConfigurationForLogger($configurationType, $loggerName)
185 {
186 // Split up the logger name (dot-separated) into its parts
187 $explodedName = explode('.', $loggerName);
188 // Search in the $TYPO3_CONF_VARS['LOG'] array
189 // for these keys, for example "writerConfiguration"
190 $configurationKey = $configurationType . 'Configuration';
191 $configuration = $GLOBALS['TYPO3_CONF_VARS']['LOG'];
192 $result = $configuration[$configurationKey] ?? [];
193 // Walk from general to special (t3lib, t3lib.db, t3lib.db.foo)
194 // and search for the most specific configuration
195 foreach ($explodedName as $partOfClassName) {
196 if (!isset($configuration[$partOfClassName])) {
197 break;
198 }
199 if (!empty($configuration[$partOfClassName][$configurationKey])) {
200 $result = $configuration[$partOfClassName][$configurationKey];
201 }
202 $configuration = $configuration[$partOfClassName];
203 }
204 // Validate the config
205 foreach ($result as $level => $unused) {
206 try {
207 LogLevel::validateLevel(LogLevel::normalizeLevel($level));
208 } catch (\Psr\Log\InvalidArgumentException $e) {
209 throw new \Psr\Log\InvalidArgumentException('The given severity level "' . htmlspecialchars($level) . '" for ' . $configurationKey . ' of logger "' . $loggerName . '" is not valid.', 1326406447);
210 }
211 }
212 return $result;
213 }
214 }