Passed
Push — master ( 210350...89de48 )
by
unknown
13:38
created

LogManager::registerLogger()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Core\Log;
17
18
use Psr\Log\InvalidArgumentException;
19
use Psr\Log\LoggerInterface;
20
use TYPO3\CMS\Core\Log\Exception\InvalidLogProcessorConfigurationException;
21
use TYPO3\CMS\Core\Log\Exception\InvalidLogWriterConfigurationException;
22
use TYPO3\CMS\Core\SingletonInterface;
23
use TYPO3\CMS\Core\Utility\GeneralUtility;
24
25
/**
26
 * Global LogManager that keeps track of global logging information.
27
 *
28
 * Inspired by java.util.logging
29
 */
30
class LogManager implements SingletonInterface, LogManagerInterface
31
{
32
    /**
33
     * @var string
34
     */
35
    const CONFIGURATION_TYPE_WRITER = 'writer';
36
37
    /**
38
     * @var string
39
     */
40
    const CONFIGURATION_TYPE_PROCESSOR = 'processor';
41
42
    /**
43
     * Loggers to retrieve them for repeated use.
44
     */
45
    protected array $loggers = [];
46
47
    /**
48
     * Default / global / root logger.
49
     */
50
    protected Logger $rootLogger;
51
52
    /**
53
     * Unique ID of the request
54
     */
55
    protected string $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 = 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 Logger Logger with name $name
88
     */
89
    public function getLogger(string $name = ''): LoggerInterface
90
    {
91
        // Transform namespaces and underscore class names to the dot-name style
92
        $separators = ['_', '\\'];
93
        $name = str_replace($separators, '.', $name);
94
95
        return $this->loggers[$name] ??= $this->makeLogger($name, $this->requestId);
96
    }
97
98
    /**
99
     * Instantiates a new logger object, with the appropriate attached writers and processors.
100
     */
101
    protected function makeLogger(string $name, string $requestId): Logger
102
    {
103
        /** @var \TYPO3\CMS\Core\Log\Logger $logger */
104
        $logger = GeneralUtility::makeInstance(Logger::class, $name, $requestId);
105
        $this->setWritersForLogger($logger);
106
        $this->setProcessorsForLogger($logger);
107
        return $logger;
108
    }
109
110
    /**
111
     * For use in unit test context only.
112
     *
113
     * @param string $name
114
     */
115
    public function registerLogger($name)
116
    {
117
        $this->loggers[$name] = null;
118
    }
119
120
    /**
121
     * For use in unit test context only.
122
     *
123
     * @return array
124
     */
125
    public function getLoggerNames()
126
    {
127
        return array_keys($this->loggers);
128
    }
129
130
    /**
131
     * Appends the writers to the given logger as configured.
132
     *
133
     * @param \TYPO3\CMS\Core\Log\Logger $logger Logger to configure
134
     */
135
    protected function setWritersForLogger(Logger $logger)
136
    {
137
        $configuration = $this->getConfigurationForLogger(self::CONFIGURATION_TYPE_WRITER, $logger->getName());
138
        foreach ($configuration as $severityLevel => $writer) {
139
            $writer = array_filter($writer, static fn (array $options) => !($options['disabled'] ?? false));
140
            foreach ($writer as $logWriterClassName => $logWriterOptions) {
141
                try {
142
                    unset($logWriterOptions['disabled']);
143
                    /** @var \TYPO3\CMS\Core\Log\Writer\WriterInterface $logWriter */
144
                    $logWriter = GeneralUtility::makeInstance($logWriterClassName, $logWriterOptions);
145
                    $logger->addWriter($severityLevel, $logWriter);
146
                } catch (InvalidArgumentException|InvalidLogWriterConfigurationException $e) {
147
                    $logger->warning('Instantiation of LogWriter "{class_name}" failed for logger {name}', [
148
                        'class_name' => $logWriterClassName,
149
                        'name' => $logger->getName(),
150
                        'exception' => $e,
151
                    ]);
152
                }
153
            }
154
        }
155
    }
156
157
    /**
158
     * Appends the processors to the given logger as configured.
159
     *
160
     * @param \TYPO3\CMS\Core\Log\Logger $logger Logger to configure
161
     */
162
    protected function setProcessorsForLogger(Logger $logger)
163
    {
164
        $configuration = $this->getConfigurationForLogger(self::CONFIGURATION_TYPE_PROCESSOR, $logger->getName());
165
        foreach ($configuration as $severityLevel => $processor) {
166
            foreach ($processor as $logProcessorClassName => $logProcessorOptions) {
167
                try {
168
                    /** @var \TYPO3\CMS\Core\Log\Processor\ProcessorInterface $logProcessor */
169
                    $logProcessor = GeneralUtility::makeInstance($logProcessorClassName, $logProcessorOptions);
170
                    $logger->addProcessor($severityLevel, $logProcessor);
171
                } catch (InvalidArgumentException|InvalidLogProcessorConfigurationException $e) {
172
                    $logger->warning('Instantiation of LogProcessor "{class_name}" failed for logger {name}', [
173
                        'class_name' => $logProcessorClassName,
174
                        'name' => $logger->getName(),
175
                        'exception' => $e,
176
                    ]);
177
                }
178
            }
179
        }
180
    }
181
182
    /**
183
     * Returns the configuration from $TYPO3_CONF_VARS['LOG'] as
184
     * hierarchical array for different components of the class hierarchy.
185
     *
186
     * @param string $configurationType Type of config to return (writer, processor)
187
     * @param string $loggerName Logger name
188
     * @throws \Psr\Log\InvalidArgumentException
189
     * @return array
190
     */
191
    protected function getConfigurationForLogger($configurationType, $loggerName)
192
    {
193
        // Split up the logger name (dot-separated) into its parts
194
        $explodedName = explode('.', $loggerName);
195
        // Search in the $TYPO3_CONF_VARS['LOG'] array
196
        // for these keys, for example "writerConfiguration"
197
        $configurationKey = $configurationType . 'Configuration';
198
        $configuration = $GLOBALS['TYPO3_CONF_VARS']['LOG'];
199
        $result = $configuration[$configurationKey] ?? [];
200
        // Walk from general to special (t3lib, t3lib.db, t3lib.db.foo)
201
        // and search for the most specific configuration
202
        foreach ($explodedName as $partOfClassName) {
203
            if (!isset($configuration[$partOfClassName])) {
204
                break;
205
            }
206
            if (!empty($configuration[$partOfClassName][$configurationKey])) {
207
                $result = $configuration[$partOfClassName][$configurationKey];
208
            }
209
            $configuration = $configuration[$partOfClassName];
210
        }
211
        // Validate the config
212
        foreach ($result as $level => $unused) {
213
            try {
214
                LogLevel::validateLevel(LogLevel::normalizeLevel($level));
215
            } catch (InvalidArgumentException $e) {
216
                throw new InvalidArgumentException('The given severity level "' . htmlspecialchars($level) . '" for ' . $configurationKey . ' of logger "' . $loggerName . '" is not valid.', 1326406447);
217
            }
218
        }
219
        return $result;
220
    }
221
}
222