requestId = $requestId; $this->rootLogger = new Logger(''); $this->loggers[''] = $this->rootLogger; } /** * For use in unit test context only. Resets the internal logger registry. */ public function reset() { $this->loggers = []; } /** * Gets a logger instance for the given name. * * \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Log\LogManager::class)->getLogger('main.sub.subsub'); * * $name can also be submitted as a underscore-separated string, which will * be converted to dots. This is useful to call this method with __CLASS__ * as parameter. * * @param string $name Logger name, empty to get the global "root" logger. * @return Logger Logger with name $name */ public function getLogger(string $name = ''): LoggerInterface { // Transform namespaces and underscore class names to the dot-name style $separators = ['_', '\\']; $name = str_replace($separators, '.', $name); return $this->loggers[$name] ??= $this->makeLogger($name); } /** * Instantiates a new logger object, with the appropriate attached writers and processors. */ protected function makeLogger(string $name): Logger { $logger = new Logger($name); $this->setWritersForLogger($logger); $this->addRequestIdProcessorToLogger($logger); $this->setProcessorsForLogger($logger); return $logger; } /** * Attaches the request id processor for all severity levels covered by the * configured writers of the given logger. The processor is registered before * any configured processor and on purpose not via $TYPO3_CONF_VARS['LOG'], * so log records are guaranteed to carry the request id, even if the global * processor configuration is overridden. Registering it for exactly the * most verbose writer level avoids raising the minimum log level, which * would create and process log records no writer ever receives. */ protected function addRequestIdProcessorToLogger(Logger $logger): void { $writerLevels = array_map(LogLevel::normalizeLevel(...), array_keys($logger->getWriters())); $minimumLevel = $writerLevels === [] ? LogLevel::normalizeLevel(\Psr\Log\LogLevel::EMERGENCY) : max($writerLevels); $logger->addProcessor(LogLevel::getInternalName($minimumLevel), new RequestIdProcessor($this->requestId)); } /** * For use in unit test context only. * * @param string $name */ public function registerLogger($name) { $this->loggers[$name] = null; } /** * For use in unit test context only. * * @return array */ public function getLoggerNames() { return array_keys($this->loggers); } /** * Appends the writers to the given logger as configured. * * @param \TYPO3\CMS\Core\Log\Logger $logger Logger to configure */ protected function setWritersForLogger(Logger $logger) { $configuration = $this->getConfigurationForLogger(self::CONFIGURATION_TYPE_WRITER, $logger->getName()); foreach ($configuration as $severityLevel => $writer) { $writer = array_filter($writer, static fn(array $options): bool => !($options['disabled'] ?? false)); foreach ($writer as $logWriterClassName => $logWriterOptions) { try { unset($logWriterOptions['disabled']); /** @var WriterInterface $logWriter */ $logWriter = GeneralUtility::makeInstance($logWriterClassName, $logWriterOptions); $logger->addWriter($severityLevel, $logWriter); } catch (InvalidArgumentException|InvalidLogWriterConfigurationException $e) { $logger->warning('Instantiation of LogWriter "{class_name}" failed for logger {name}', [ 'class_name' => $logWriterClassName, 'name' => $logger->getName(), 'exception' => $e, ]); } } } } /** * Appends the processors to the given logger as configured. * * @param \TYPO3\CMS\Core\Log\Logger $logger Logger to configure */ protected function setProcessorsForLogger(Logger $logger) { $configuration = $this->getConfigurationForLogger(self::CONFIGURATION_TYPE_PROCESSOR, $logger->getName()); foreach ($configuration as $severityLevel => $processor) { foreach ($processor as $logProcessorClassName => $logProcessorOptions) { try { /** @var ProcessorInterface $logProcessor */ $logProcessor = GeneralUtility::makeInstance($logProcessorClassName, $logProcessorOptions); $logger->addProcessor($severityLevel, $logProcessor); } catch (InvalidArgumentException|InvalidLogProcessorConfigurationException $e) { $logger->warning('Instantiation of LogProcessor "{class_name}" failed for logger {name}', [ 'class_name' => $logProcessorClassName, 'name' => $logger->getName(), 'exception' => $e, ]); } } } } /** * Returns the configuration from $TYPO3_CONF_VARS['LOG'] as * hierarchical array for different components of the class hierarchy. * * @param string $configurationType Type of config to return (writer, processor) * @param string $loggerName Logger name * @throws \Psr\Log\InvalidArgumentException * @return array */ protected function getConfigurationForLogger($configurationType, $loggerName) { // Split up the logger name (dot-separated) into its parts $explodedName = explode('.', $loggerName); // Search in the $TYPO3_CONF_VARS['LOG'] array // for these keys, for example "writerConfiguration" $configurationKey = $configurationType . 'Configuration'; $configuration = $GLOBALS['TYPO3_CONF_VARS']['LOG'] ?? []; $result = $configuration[$configurationKey] ?? []; // Walk from general to special (t3lib, t3lib.db, t3lib.db.foo) // and search for the most specific configuration foreach ($explodedName as $partOfClassName) { if (!isset($configuration[$partOfClassName])) { break; } if (!empty($configuration[$partOfClassName][$configurationKey])) { $result = $configuration[$partOfClassName][$configurationKey]; } $configuration = $configuration[$partOfClassName]; } // Validate the config foreach ($result as $level => $unused) { try { LogLevel::validateLevel(LogLevel::normalizeLevel($level)); } catch (InvalidArgumentException $e) { throw new InvalidArgumentException('The given severity level "' . htmlspecialchars($level) . '" for ' . $configurationKey . ' of logger "' . $loggerName . '" is not valid.', 1326406447); } } return $result; } }