TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log;
|
||||
|
||||
/**
|
||||
* Attribute for Logger channel declarations
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_PARAMETER | \Attribute::TARGET_CLASS)]
|
||||
class Channel
|
||||
{
|
||||
public string $name;
|
||||
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log;
|
||||
|
||||
/**
|
||||
* An exception when something is wrong with the file handling
|
||||
*/
|
||||
class Exception extends \TYPO3\CMS\Core\Exception {}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Exception;
|
||||
|
||||
use TYPO3\CMS\Core\Log\Exception;
|
||||
|
||||
/**
|
||||
* An exception when something is wrong with the configuration for a LogProcessor
|
||||
*/
|
||||
class InvalidLogProcessorConfigurationException extends Exception {}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Exception;
|
||||
|
||||
use TYPO3\CMS\Core\Log\Exception;
|
||||
|
||||
/**
|
||||
* An exception when something is wrong with the configuration for a LogWriter
|
||||
*/
|
||||
class InvalidLogWriterConfigurationException extends Exception {}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log;
|
||||
|
||||
/**
|
||||
* Helper for handling both serialize()/unserialize() and json_encode()/json_decode()
|
||||
* when migrating to json-encoded strings.
|
||||
*/
|
||||
trait LogDataTrait
|
||||
{
|
||||
/**
|
||||
* Useful for handling old serialized data, which might have been migrated to JSON encoded
|
||||
* properties already.
|
||||
*/
|
||||
protected function unserializeLogData(mixed $logData): ?array
|
||||
{
|
||||
// The @ symbol avoids an E_NOTICE when unserialize() fails
|
||||
$cleanedUpData = @unserialize((string)$logData, ['allowed_classes' => false]);
|
||||
if ($cleanedUpData === false) {
|
||||
$cleanedUpData = json_decode((string)$logData, true);
|
||||
}
|
||||
return is_array($cleanedUpData) ? $cleanedUpData : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces a string with placeholders (%s or {myPlaceholder}) with its substitutes.
|
||||
*/
|
||||
protected function formatLogDetails(string $detailString, mixed $substitutes): string
|
||||
{
|
||||
if (!is_array($substitutes)) {
|
||||
$substitutes = $this->unserializeLogData($substitutes) ?? [];
|
||||
}
|
||||
return self::formatLogDetailsStatic($detailString, $substitutes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static version for ViewHelpers etc.
|
||||
*
|
||||
* Replaces a string with placeholders (%s or {myPlaceholder}) with its substitutes.
|
||||
*/
|
||||
protected static function formatLogDetailsStatic(string $detailString, array $substitutes): string
|
||||
{
|
||||
// Handles placeholders with "%" first
|
||||
try {
|
||||
$detailString = vsprintf($detailString, $substitutes);
|
||||
} catch (\ValueError|\ArgumentCountError) {
|
||||
// Ignore if $substitutes doesn't contain the number of "%" found in $detailString
|
||||
}
|
||||
|
||||
// Handles placeholders with "{myPlaceholder}"
|
||||
$detailString = preg_replace_callback('/{([A-z]+)}/', static function (array $matches) use ($substitutes) {
|
||||
// $matches[0] contains the unsubstituted placeholder
|
||||
return $substitutes[$matches[1]] ?? $matches[0];
|
||||
}, $detailString);
|
||||
|
||||
// Remove possible pending %s
|
||||
return str_replace('%s', '', (string)$detailString);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log;
|
||||
|
||||
use Psr\Log\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Log levels according to RFC 3164
|
||||
*/
|
||||
class LogLevel extends \Psr\Log\LogLevel
|
||||
{
|
||||
/**
|
||||
* Reverse look up of log level to level name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $levels = [
|
||||
self::EMERGENCY,
|
||||
self::ALERT,
|
||||
self::CRITICAL,
|
||||
self::ERROR,
|
||||
self::WARNING,
|
||||
self::NOTICE,
|
||||
self::INFO,
|
||||
self::DEBUG,
|
||||
];
|
||||
|
||||
/**
|
||||
* Resolves the name of a log level and returns it in upper case letters
|
||||
*
|
||||
* @param int $level Log level.
|
||||
* @return string Log level name.
|
||||
*/
|
||||
public static function getName(int $level): string
|
||||
{
|
||||
return strtoupper(static::getInternalName($level));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the name of the log level and returns its internal string representation
|
||||
*/
|
||||
public static function getInternalName(int $level): string
|
||||
{
|
||||
self::validateLevel($level);
|
||||
return static::$levels[$level];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a level for validity,
|
||||
* whether it is an integer and in the range of 0-7.
|
||||
*
|
||||
* @param int $level log level to validate
|
||||
* @return bool TRUE if the given log level is valid, FALSE otherwise
|
||||
*/
|
||||
public static function isValidLevel(int $level): bool
|
||||
{
|
||||
return isset(static::$levels[$level]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a log level.
|
||||
*
|
||||
* @param int $level log level to validate
|
||||
* @throws InvalidArgumentException if the given log level is invalid
|
||||
*/
|
||||
public static function validateLevel(int $level): void
|
||||
{
|
||||
if (!self::isValidLevel($level)) {
|
||||
throw new InvalidArgumentException('Invalid Log Level ' . $level, 1321637121);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes level by converting it from string to integer
|
||||
*
|
||||
* @param string|int $level
|
||||
*/
|
||||
public static function normalizeLevel($level): int
|
||||
{
|
||||
if (is_string($level)) {
|
||||
if (!defined(__CLASS__ . '::' . strtoupper($level))) {
|
||||
throw new InvalidArgumentException('Invalid Log Level ' . $level, 1550247164);
|
||||
}
|
||||
return array_search(strtolower($level), self::$levels, true);
|
||||
}
|
||||
|
||||
return (int)$level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all log levels at least as severe as the specified level.
|
||||
*
|
||||
* @param int|string $level
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function atLeast($level): array
|
||||
{
|
||||
$level = self::normalizeLevel($level);
|
||||
return array_filter(self::$levels, static fn(int $intLevel): bool => $intLevel <= $level, ARRAY_FILTER_USE_KEY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log;
|
||||
|
||||
use Psr\Log\InvalidArgumentException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use TYPO3\CMS\Core\Core\RequestId;
|
||||
use TYPO3\CMS\Core\Log\Exception\InvalidLogProcessorConfigurationException;
|
||||
use TYPO3\CMS\Core\Log\Exception\InvalidLogWriterConfigurationException;
|
||||
use TYPO3\CMS\Core\Log\Processor\ProcessorInterface;
|
||||
use TYPO3\CMS\Core\Log\Processor\RequestIdProcessor;
|
||||
use TYPO3\CMS\Core\Log\Writer\WriterInterface;
|
||||
use TYPO3\CMS\Core\SingletonInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Global LogManager that keeps track of global logging information.
|
||||
*
|
||||
* Inspired by java.util.logging
|
||||
*/
|
||||
class LogManager implements SingletonInterface, LogManagerInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const CONFIGURATION_TYPE_WRITER = 'writer';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const CONFIGURATION_TYPE_PROCESSOR = 'processor';
|
||||
|
||||
/**
|
||||
* Loggers to retrieve them for repeated use.
|
||||
*/
|
||||
protected array $loggers = [];
|
||||
|
||||
/**
|
||||
* Default / global / root logger.
|
||||
*/
|
||||
protected Logger $rootLogger;
|
||||
|
||||
/**
|
||||
* Unique ID of the request
|
||||
*/
|
||||
protected RequestId $requestId;
|
||||
|
||||
public function __construct(RequestId $requestId = new RequestId())
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* LogManager Contract for delivering log instances.
|
||||
*/
|
||||
interface LogManagerInterface
|
||||
{
|
||||
/**
|
||||
* Gets a logger instance for the given name.
|
||||
*/
|
||||
public function getLogger(string $name = ''): LoggerInterface;
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log;
|
||||
|
||||
/**
|
||||
* Log record
|
||||
*/
|
||||
final class LogRecord implements \ArrayAccess
|
||||
{
|
||||
/**
|
||||
* Unique ID of the request
|
||||
*/
|
||||
private string $requestId = '';
|
||||
|
||||
/**
|
||||
* Creation timestamp with microseconds
|
||||
*/
|
||||
private float $created = 0.0;
|
||||
|
||||
/**
|
||||
* The component where the record was created
|
||||
*/
|
||||
private string $component = '';
|
||||
|
||||
/**
|
||||
* Severity level
|
||||
*/
|
||||
private string $level = \Psr\Log\LogLevel::INFO;
|
||||
|
||||
/**
|
||||
* Log message one-liner
|
||||
*/
|
||||
private string $message = '';
|
||||
|
||||
/**
|
||||
* Additional log data
|
||||
*/
|
||||
private array $data = [];
|
||||
|
||||
/**
|
||||
* Gettable properties for ArrayAccess
|
||||
*/
|
||||
private array $gettableProperties = [
|
||||
'requestId',
|
||||
'created',
|
||||
'component',
|
||||
'level',
|
||||
'message',
|
||||
'data',
|
||||
];
|
||||
|
||||
/**
|
||||
* Settable properties for ArrayAccess
|
||||
*/
|
||||
private array $settableProperties = [
|
||||
'level',
|
||||
'message',
|
||||
'data',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param string $component Affected component
|
||||
* @param string $level Severity level (see \TYPO3\CMS\Core\Log\LogLevel)
|
||||
* @param string|\Stringable $message Log message
|
||||
* @param array $data Additional data
|
||||
* @param string $requestId Unique ID of the request
|
||||
*/
|
||||
public function __construct(string $component, string $level, string|\Stringable $message, array $data = [], string $requestId = '')
|
||||
{
|
||||
$this->setRequestId($requestId)
|
||||
->setCreated(microtime(true))
|
||||
->setComponent($component)
|
||||
->setLevel($level)
|
||||
->setMessage($message)
|
||||
->setData($data);
|
||||
}
|
||||
|
||||
public function setComponent(string $component): self
|
||||
{
|
||||
$this->component = $component;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getComponent(): string
|
||||
{
|
||||
return $this->component;
|
||||
}
|
||||
|
||||
public function setCreated(float $created): self
|
||||
{
|
||||
$this->created = $created;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreated(): float
|
||||
{
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \TYPO3\CMS\Core\Log\LogLevel
|
||||
*/
|
||||
public function setLevel(string $level): self
|
||||
{
|
||||
LogLevel::validateLevel(LogLevel::normalizeLevel($level));
|
||||
$this->level = $level;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \TYPO3\CMS\Core\Log\LogLevel
|
||||
*/
|
||||
public function getLevel(): string
|
||||
{
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
public function setData(array $data): self
|
||||
{
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds additional log data to already existing data
|
||||
* and overwrites previously data using the same array keys.
|
||||
*/
|
||||
public function addData(array $data): self
|
||||
{
|
||||
$this->data = array_merge($this->data, $data);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMessage(string|\Stringable $message): self
|
||||
{
|
||||
$this->message = (string)$message;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMessage(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function setRequestId(string $requestId): self
|
||||
{
|
||||
$this->requestId = $requestId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRequestId(): string
|
||||
{
|
||||
return $this->requestId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert record to string for simple output, like echo().
|
||||
* Contents of data array is appended as JSON-encoded string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
$timestamp = date('r', (int)$this->created);
|
||||
$levelName = strtoupper($this->level);
|
||||
$data = '';
|
||||
if (!empty($this->data)) {
|
||||
// According to PSR3 the exception-key may hold an \Exception
|
||||
// Since json_encode() does not encode an exception, we run the _toString() here
|
||||
if (isset($this->data['exception']) && $this->data['exception'] instanceof \Exception) {
|
||||
$this->data['exception'] = (string)$this->data['exception'];
|
||||
}
|
||||
$data = '- ' . json_encode($this->data);
|
||||
}
|
||||
$logRecordString = sprintf(
|
||||
'%s [%s] request="%s" component="%s": %s %s',
|
||||
$timestamp,
|
||||
$levelName,
|
||||
$this->requestId,
|
||||
$this->component,
|
||||
$this->message,
|
||||
$data
|
||||
);
|
||||
return $logRecordString;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'requestId' => $this->requestId,
|
||||
'created' => $this->created,
|
||||
'component' => $this->component,
|
||||
'level' => $this->level,
|
||||
'message' => $this->message,
|
||||
'data' => $this->data,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether an offset exists, required by ArrayAccess interface
|
||||
*/
|
||||
public function offsetExists(mixed $offset): bool
|
||||
{
|
||||
$offsetExists = false;
|
||||
if (in_array($offset, $this->gettableProperties, true) && isset($this->{$offset})) {
|
||||
$offsetExists = true;
|
||||
}
|
||||
return $offsetExists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to retrieve, required by ArrayAccess interface
|
||||
*/
|
||||
public function offsetGet(mixed $offset): mixed
|
||||
{
|
||||
if (!in_array($offset, $this->gettableProperties, true)) {
|
||||
return null;
|
||||
}
|
||||
return $this->{$offset};
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to set, required by ArrayAccess interface
|
||||
*/
|
||||
public function offsetSet(mixed $offset, mixed $value): void
|
||||
{
|
||||
if (in_array($offset, $this->settableProperties, true)) {
|
||||
$this->{$offset} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to unset, required by ArrayAccess interface
|
||||
*/
|
||||
public function offsetUnset(mixed $offset): void
|
||||
{
|
||||
if (in_array($offset, $this->settableProperties, true)) {
|
||||
unset($this->{$offset});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LoggerTrait;
|
||||
use TYPO3\CMS\Core\Log\Processor\ProcessorInterface;
|
||||
use TYPO3\CMS\Core\Log\Writer\WriterInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Logger to log events and data for different components.
|
||||
*/
|
||||
final class Logger implements LoggerInterface
|
||||
{
|
||||
use LoggerTrait;
|
||||
|
||||
/**
|
||||
* Logger name or component for which this logger is meant to be used for.
|
||||
*
|
||||
* This should be a dot-separated name and should normally be based on
|
||||
* the class name or the name of a subsystem, such as
|
||||
* core.t3lib.cache.manager, core.backend.workspaces or extension.news
|
||||
*/
|
||||
protected string $name = '';
|
||||
|
||||
/**
|
||||
* Minimum log level, anything below this level will be ignored.
|
||||
*/
|
||||
protected int $minimumLogLevel;
|
||||
|
||||
/**
|
||||
* Writers used by this logger
|
||||
*/
|
||||
protected array $writers = [];
|
||||
|
||||
/**
|
||||
* Processors used by this logger
|
||||
*/
|
||||
protected array $processors = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $name A name for the logger.
|
||||
*/
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->minimumLogLevel = LogLevel::normalizeLevel(LogLevel::EMERGENCY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-initialize instance with creating a new instance with up to date information
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
$newLogger = GeneralUtility::makeInstance(LogManager::class)->getLogger($this->name);
|
||||
$this->minimumLogLevel = $newLogger->minimumLogLevel;
|
||||
$this->writers = $newLogger->writers;
|
||||
$this->processors = $newLogger->processors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove everything except the name, to be able to restore it on wakeup
|
||||
*/
|
||||
public function __sleep(): array
|
||||
{
|
||||
return ['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum log level for which log records are written.
|
||||
*
|
||||
* @param int $level Minimum log level
|
||||
* @return \TYPO3\CMS\Core\Log\Logger $this
|
||||
*/
|
||||
protected function setMinimumLogLevel($level)
|
||||
{
|
||||
LogLevel::validateLevel($level);
|
||||
$this->minimumLogLevel = $level;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum log level for which log records are written.
|
||||
*
|
||||
* @return int Minimum log level
|
||||
*/
|
||||
protected function getMinimumLogLevel()
|
||||
{
|
||||
return $this->minimumLogLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the logger's name.
|
||||
*
|
||||
* @return string Logger name.
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a writer to this logger
|
||||
*
|
||||
* @param \TYPO3\CMS\Core\Log\Writer\WriterInterface $writer Writer object
|
||||
* @return \TYPO3\CMS\Core\Log\Logger $this
|
||||
*/
|
||||
public function addWriter(string $minimumLevel, WriterInterface $writer)
|
||||
{
|
||||
$minLevelAsNumber = LogLevel::normalizeLevel($minimumLevel);
|
||||
// Cycle through all the log levels which are as severe as or higher
|
||||
// than $minimumLevel and add $writer to each severity level
|
||||
foreach (LogLevel::atLeast($minLevelAsNumber) as $levelName) {
|
||||
$this->writers[$levelName] ??= [];
|
||||
$this->writers[$levelName][] = $writer;
|
||||
}
|
||||
if ($minLevelAsNumber > $this->getMinimumLogLevel()) {
|
||||
$this->setMinimumLogLevel($minLevelAsNumber);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all configured writers indexed by log level
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getWriters()
|
||||
{
|
||||
return $this->writers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a processor to the logger.
|
||||
*
|
||||
* @param \TYPO3\CMS\Core\Log\Processor\ProcessorInterface $processor The processor to add.
|
||||
*/
|
||||
public function addProcessor(string $minimumLevel, ProcessorInterface $processor)
|
||||
{
|
||||
$minLevelAsNumber = LogLevel::normalizeLevel($minimumLevel);
|
||||
LogLevel::validateLevel($minLevelAsNumber);
|
||||
// Cycle through all the log levels which are as severe as or higher
|
||||
// than $minimumLevel and add $processor to each severity level
|
||||
for ($logLevelWhichTriggersProcessor = LogLevel::normalizeLevel(\Psr\Log\LogLevel::EMERGENCY); $logLevelWhichTriggersProcessor <= $minLevelAsNumber; $logLevelWhichTriggersProcessor++) {
|
||||
$logLevelName = LogLevel::getInternalName($logLevelWhichTriggersProcessor);
|
||||
$this->processors[$logLevelName] ??= [];
|
||||
$this->processors[$logLevelName][] = $processor;
|
||||
}
|
||||
if ($minLevelAsNumber > $this->getMinimumLogLevel()) {
|
||||
$this->setMinimumLogLevel($minLevelAsNumber);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all added processors indexed by log level
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getProcessors()
|
||||
{
|
||||
return $this->processors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a log record.
|
||||
*
|
||||
* @param int|string $level Log level. Value according to \TYPO3\CMS\Core\Log\LogLevel. Alternatively accepts a string.
|
||||
* @param string|\Stringable $message Log message.
|
||||
* @param array $data Additional data to log
|
||||
*/
|
||||
public function log($level, string|\Stringable $message, array $data = []): void
|
||||
{
|
||||
$level = LogLevel::normalizeLevel($level);
|
||||
LogLevel::validateLevel($level);
|
||||
if ($level > $this->minimumLogLevel) {
|
||||
return;
|
||||
}
|
||||
$record = new LogRecord($this->name, LogLevel::getInternalName($level), $message, $data);
|
||||
$record = $this->callProcessors($record);
|
||||
$this->writeLog($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls all processors and returns log record
|
||||
*
|
||||
* @param \TYPO3\CMS\Core\Log\LogRecord $record Record to process
|
||||
* @throws \RuntimeException
|
||||
* @return \TYPO3\CMS\Core\Log\LogRecord Processed log record
|
||||
*/
|
||||
protected function callProcessors(LogRecord $record)
|
||||
{
|
||||
/** @var ProcessorInterface $processor */
|
||||
foreach ($this->processors[$record->getLevel()] ?? [] as $processor) {
|
||||
$processedRecord = $processor->processLogRecord($record);
|
||||
if (!$processedRecord instanceof LogRecord) {
|
||||
throw new \RuntimeException('Processor ' . get_class($processor) . ' returned invalid data. Instance of TYPO3\\CMS\\Core\\Log\\LogRecord expected', 1343593398);
|
||||
}
|
||||
$record = $processedRecord;
|
||||
}
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes the \TYPO3\CMS\Core\Log\LogRecord to all registered writers.
|
||||
*/
|
||||
protected function writeLog(LogRecord $record)
|
||||
{
|
||||
/** @var WriterInterface $writer */
|
||||
foreach ($this->writers[$record->getLevel()] ?? [] as $writer) {
|
||||
$writer->writeLog($record);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Processor;
|
||||
|
||||
/**
|
||||
* Common memory processor methods.
|
||||
*/
|
||||
abstract class AbstractMemoryProcessor extends AbstractProcessor
|
||||
{
|
||||
/**
|
||||
* Allocated memory usage type to use
|
||||
* If set, the real size of memory allocated from system is used.
|
||||
* Otherwise the memory used by emalloc() is used.
|
||||
*
|
||||
* @var bool
|
||||
* @see memory_get_usage()
|
||||
* @see memory_get_peak_usage()
|
||||
*/
|
||||
protected $realMemoryUsage = true;
|
||||
|
||||
/**
|
||||
* Whether the size is formatted, e.g. in megabytes
|
||||
*
|
||||
* @var bool
|
||||
* @see \TYPO3\CMS\Core\Utility\GeneralUtility::formatSize()
|
||||
*/
|
||||
protected $formatSize = true;
|
||||
|
||||
/**
|
||||
* Sets the allocated memory usage type
|
||||
*
|
||||
* @param bool $realMemoryUsage Which allocated memory type to use
|
||||
*/
|
||||
public function setRealMemoryUsage($realMemoryUsage)
|
||||
{
|
||||
$this->realMemoryUsage = (bool)$realMemoryUsage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the allocated memory usage type
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getRealMemoryUsage()
|
||||
{
|
||||
return $this->realMemoryUsage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether size should be formatted
|
||||
*
|
||||
* @param bool $formatSize
|
||||
*/
|
||||
public function setFormatSize($formatSize)
|
||||
{
|
||||
$this->formatSize = (bool)$formatSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether size should be formatted
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getFormatSize()
|
||||
{
|
||||
return $this->formatSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Processor;
|
||||
|
||||
use TYPO3\CMS\Core\Log\Exception\InvalidLogProcessorConfigurationException;
|
||||
|
||||
/**
|
||||
* Abstract implementation of a log processor
|
||||
*/
|
||||
abstract class AbstractProcessor implements ProcessorInterface
|
||||
{
|
||||
/**
|
||||
* Constructs this log processor
|
||||
*
|
||||
* @param array $options Configuration options - depends on the actual processor
|
||||
* @throws \TYPO3\CMS\Core\Log\Exception\InvalidLogProcessorConfigurationException
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
foreach ($options as $optionKey => $optionValue) {
|
||||
$methodName = 'set' . ucfirst($optionKey);
|
||||
if (method_exists($this, $methodName)) {
|
||||
$this->{$methodName}($optionValue);
|
||||
} else {
|
||||
throw new InvalidLogProcessorConfigurationException('Invalid LogProcessor configuration option "' . $optionKey . '" for log processor of type "' . static::class . '"', 1321696151);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Processor;
|
||||
|
||||
use TYPO3\CMS\Core\Log\LogRecord;
|
||||
|
||||
/**
|
||||
* Introspection processor to automatically add where the log record came from.
|
||||
*/
|
||||
class IntrospectionProcessor extends AbstractProcessor
|
||||
{
|
||||
/**
|
||||
* Add the full backtrace to the log entry or
|
||||
* just the last entry of the backtrace
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $appendFullBackTrace = false;
|
||||
|
||||
/**
|
||||
* Number of entries to shift from the backtrace
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $shiftBackTraceLevel = 0;
|
||||
|
||||
/**
|
||||
* Temporary storage of the preceding backtrace line number
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $precedingBacktraceLine = '';
|
||||
|
||||
/**
|
||||
* Temporary storage of the preceding backtrace file
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $precedingBacktraceFile = '';
|
||||
|
||||
/**
|
||||
* Set the number of levels to be shift from the backtrace
|
||||
*
|
||||
* @param int $shiftBackTraceLevel Numbers of levels to shift
|
||||
* @return IntrospectionProcessor
|
||||
*/
|
||||
public function setShiftBackTraceLevel($shiftBackTraceLevel)
|
||||
{
|
||||
$this->shiftBackTraceLevel = (int)$shiftBackTraceLevel;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the full backtrace should be added to the log or just the last item
|
||||
*
|
||||
* @param bool $appendFullBackTrace If the full backtrace should be added
|
||||
* @return IntrospectionProcessor
|
||||
*/
|
||||
public function setAppendFullBackTrace($appendFullBackTrace)
|
||||
{
|
||||
$this->appendFullBackTrace = (bool)$appendFullBackTrace;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add debug backtrace information to logRecord
|
||||
* It adds: filepath, line number, class and function name
|
||||
*
|
||||
* @param LogRecord $logRecord The log record to process
|
||||
* @return LogRecord The processed log record with additional data
|
||||
* @see debug_backtrace()
|
||||
*/
|
||||
public function processLogRecord(LogRecord $logRecord)
|
||||
{
|
||||
$trace = $this->getDebugBacktrace();
|
||||
|
||||
// skip TYPO3\CMS\Core\Log classes
|
||||
foreach ($trace as $traceEntry) {
|
||||
if (isset($traceEntry['class']) && str_contains($traceEntry['class'], 'TYPO3\\CMS\\Core\\Log')) {
|
||||
$trace = $this->shiftBacktraceLevel($trace);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// shift a given number of entries from the trace
|
||||
for ($i = 0; $i < $this->shiftBackTraceLevel; $i++) {
|
||||
// shift only if afterwards there is at least one entry left after.
|
||||
if (count($trace) > 1) {
|
||||
$trace = $this->shiftBacktraceLevel($trace);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->appendFullBackTrace) {
|
||||
// Add the line and file of the last entry that has these information
|
||||
// to the first backtrace entry if it does not have this information.
|
||||
// This is required in case we have shifted entries and the first entry
|
||||
// is now a call_user_func that does not contain the line and file information.
|
||||
if (!isset($trace[0]['line'])) {
|
||||
$trace[0] = ['line' => $this->precedingBacktraceLine] + $trace[0];
|
||||
}
|
||||
if (!isset($trace[0]['file'])) {
|
||||
$trace[0] = ['file' => $this->precedingBacktraceFile] + $trace[0];
|
||||
}
|
||||
|
||||
$logRecord->addData([
|
||||
'backtrace' => $trace,
|
||||
]);
|
||||
} else {
|
||||
$logRecord->addData([
|
||||
'file' => $trace[0]['file'] ?? null,
|
||||
'line' => $trace[0]['line'] ?? null,
|
||||
'class' => $trace[0]['class'] ?? null,
|
||||
'function' => $trace[0]['function'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
return $logRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift the first item from the backtrace
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function shiftBacktraceLevel(array $backtrace)
|
||||
{
|
||||
if (isset($backtrace[0]['file'])) {
|
||||
$this->precedingBacktraceFile = $backtrace[0]['file'];
|
||||
}
|
||||
if (isset($backtrace[0]['line'])) {
|
||||
$this->precedingBacktraceLine = $backtrace[0]['line'];
|
||||
}
|
||||
array_shift($backtrace);
|
||||
|
||||
return $backtrace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the debug backtrace
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDebugBacktrace()
|
||||
{
|
||||
return debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Processor;
|
||||
|
||||
use TYPO3\CMS\Core\Log\LogRecord;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Memory peak usage processor methods.
|
||||
*/
|
||||
class MemoryPeakUsageProcessor extends AbstractMemoryProcessor
|
||||
{
|
||||
/**
|
||||
* Processes a log record and adds memory peak usage information.
|
||||
*
|
||||
* @param \TYPO3\CMS\Core\Log\LogRecord $logRecord The log record to process
|
||||
* @return \TYPO3\CMS\Core\Log\LogRecord The processed log record with additional data
|
||||
* @see memory_get_peak_usage()
|
||||
*/
|
||||
public function processLogRecord(LogRecord $logRecord)
|
||||
{
|
||||
$bytes = memory_get_peak_usage($this->getRealMemoryUsage());
|
||||
if ($this->formatSize) {
|
||||
$size = GeneralUtility::formatSize($bytes);
|
||||
} else {
|
||||
$size = $bytes;
|
||||
}
|
||||
$logRecord->addData([
|
||||
'memoryPeakUsage' => $size,
|
||||
]);
|
||||
return $logRecord;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Processor;
|
||||
|
||||
use TYPO3\CMS\Core\Log\LogRecord;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Memory usage processor methods.
|
||||
*/
|
||||
class MemoryUsageProcessor extends AbstractMemoryProcessor
|
||||
{
|
||||
/**
|
||||
* Processes a log record and adds memory usage information.
|
||||
*
|
||||
* @param \TYPO3\CMS\Core\Log\LogRecord $logRecord The log record to process
|
||||
* @return \TYPO3\CMS\Core\Log\LogRecord The processed log record with additional data
|
||||
* @see memory_get_usage()
|
||||
*/
|
||||
public function processLogRecord(LogRecord $logRecord)
|
||||
{
|
||||
$bytes = memory_get_usage($this->getRealMemoryUsage());
|
||||
if ($this->formatSize) {
|
||||
$size = GeneralUtility::formatSize($bytes);
|
||||
} else {
|
||||
$size = $bytes;
|
||||
}
|
||||
$logRecord->addData([
|
||||
'memoryUsage' => $size,
|
||||
]);
|
||||
return $logRecord;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Processor;
|
||||
|
||||
use TYPO3\CMS\Core\Log\LogRecord;
|
||||
|
||||
/**
|
||||
* A log processor that does nothing. Used in unit tests.
|
||||
*/
|
||||
class NullProcessor extends AbstractProcessor
|
||||
{
|
||||
/**
|
||||
* Processes a log record and returns the same.
|
||||
*
|
||||
* @param \TYPO3\CMS\Core\Log\LogRecord $logRecord The log record to process
|
||||
* @return \TYPO3\CMS\Core\Log\LogRecord The processed log record with additional data
|
||||
*/
|
||||
public function processLogRecord(LogRecord $logRecord)
|
||||
{
|
||||
return $logRecord;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Processor;
|
||||
|
||||
use TYPO3\CMS\Core\Log\LogRecord;
|
||||
|
||||
/**
|
||||
* Log processor interface
|
||||
*
|
||||
* Processors provide additional data in an automatic way, without having to
|
||||
* collect that data yourself.
|
||||
*/
|
||||
interface ProcessorInterface
|
||||
{
|
||||
/**
|
||||
* Processes a log record and adds additional data.
|
||||
*
|
||||
* @param \TYPO3\CMS\Core\Log\LogRecord $logRecord The log record to process
|
||||
* @return \TYPO3\CMS\Core\Log\LogRecord The processed log record with additional data
|
||||
*/
|
||||
public function processLogRecord(LogRecord $logRecord);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Processor;
|
||||
|
||||
use TYPO3\CMS\Core\Core\RequestId;
|
||||
use TYPO3\CMS\Core\Log\LogRecord;
|
||||
|
||||
/**
|
||||
* Adds the unique ID of the current request to log records, in order
|
||||
* to correlate all log entries written during a single request.
|
||||
*/
|
||||
final class RequestIdProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(private readonly RequestId $requestId) {}
|
||||
|
||||
public function processLogRecord(LogRecord $logRecord): LogRecord
|
||||
{
|
||||
return $logRecord->setRequestId((string)$this->requestId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Processor;
|
||||
|
||||
use TYPO3\CMS\Core\Http\NormalizedParams;
|
||||
use TYPO3\CMS\Core\Log\LogRecord;
|
||||
|
||||
/**
|
||||
* Web log processor to automatically add web request related data to a log
|
||||
* record.
|
||||
*/
|
||||
class WebProcessor extends AbstractProcessor
|
||||
{
|
||||
public function processLogRecord(LogRecord $logRecord): LogRecord
|
||||
{
|
||||
$normalizedParams = ($GLOBALS['TYPO3_REQUEST'] ?? null)?->getAttribute('normalizedParams');
|
||||
if ($normalizedParams instanceof NormalizedParams) {
|
||||
$logRecord->addData([
|
||||
'HTTP_HOST' => $normalizedParams->getHttpHost(),
|
||||
'TYPO3_HOST_ONLY' => $normalizedParams->getRequestHostOnly(),
|
||||
'TYPO3_PORT' => $normalizedParams->getRequestPort(),
|
||||
'PATH_INFO' => $normalizedParams->getPathInfo(),
|
||||
'QUERY_STRING' => $normalizedParams->getQueryString(),
|
||||
'REQUEST_URI' => $normalizedParams->getRequestUri(),
|
||||
'HTTP_REFERER' => $normalizedParams->getHttpReferer(),
|
||||
'TYPO3_REQUEST_HOST' => $normalizedParams->getRequestHost(),
|
||||
'TYPO3_REQUEST_URL' => $normalizedParams->getRequestUrl(),
|
||||
'TYPO3_REQUEST_SCRIPT' => $normalizedParams->getRequestScript(),
|
||||
'TYPO3_REQUEST_DIR' => $normalizedParams->getRequestDir(),
|
||||
'TYPO3_SITE_URL' => $normalizedParams->getSiteUrl(),
|
||||
'TYPO3_SITE_SCRIPT' => $normalizedParams->getSiteScript(),
|
||||
'TYPO3_SSL' => $normalizedParams->isHttps(),
|
||||
'TYPO3_REV_PROXY' => $normalizedParams->isBehindReverseProxy(),
|
||||
'SCRIPT_NAME' => $normalizedParams->getScriptName(),
|
||||
'TYPO3_DOCUMENT_ROOT' => $normalizedParams->getDocumentRoot(),
|
||||
'SCRIPT_FILENAME' => $normalizedParams->getScriptFilename(),
|
||||
'REMOTE_ADDR' => $normalizedParams->getRemoteAddress(),
|
||||
'REMOTE_HOST' => $normalizedParams->getRemoteHost(),
|
||||
'HTTP_USER_AGENT' => $normalizedParams->getHttpUserAgent(),
|
||||
'HTTP_ACCEPT_LANGUAGE' => $normalizedParams->getHttpAcceptLanguage(),
|
||||
]);
|
||||
}
|
||||
return $logRecord;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Writer;
|
||||
|
||||
use TYPO3\CMS\Core\Log\Exception\InvalidLogWriterConfigurationException;
|
||||
use TYPO3\CMS\Core\Security\BlockSerializationTrait;
|
||||
|
||||
/**
|
||||
* Abstract implementation of a log writer
|
||||
*/
|
||||
abstract class AbstractWriter implements WriterInterface
|
||||
{
|
||||
use BlockSerializationTrait;
|
||||
|
||||
/**
|
||||
* Constructs this log writer
|
||||
*
|
||||
* @param array $options Configuration options - depends on the actual log writer
|
||||
* @throws \TYPO3\CMS\Core\Log\Exception\InvalidLogWriterConfigurationException
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
foreach ($options as $optionKey => $optionValue) {
|
||||
$methodName = 'set' . ucfirst($optionKey);
|
||||
if (method_exists($this, $methodName)) {
|
||||
$this->{$methodName}($optionValue);
|
||||
} else {
|
||||
throw new InvalidLogWriterConfigurationException('Invalid LogWriter configuration option "' . $optionKey . '" for log writer of type "' . static::class . '"', 1321696152);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates context values into the message placeholders.
|
||||
*/
|
||||
protected function interpolate(string $message, array $context = []): string
|
||||
{
|
||||
// Build a replacement array with braces around the context keys.
|
||||
$replace = [];
|
||||
foreach ($context as $key => $val) {
|
||||
if (!is_array($val) && !is_null($val) && (!is_object($val) || method_exists($val, '__toString'))) {
|
||||
$replace['{' . $key . '}'] = $this->formatContextValue($val);
|
||||
}
|
||||
}
|
||||
|
||||
// Interpolate replacement values into the message and return.
|
||||
return strtr($message, $replace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape or quote a value from the context appropriate for the output.
|
||||
*
|
||||
* Note: In some output cases, escaping should not be done here but later on output,
|
||||
* such as if it's being written to a database for later display.
|
||||
*
|
||||
* @param string $value
|
||||
*/
|
||||
protected function formatContextValue(string $value): string
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an exception into a string.
|
||||
*
|
||||
* The format here is nearly the same as just casting an exception to a string,
|
||||
* but omits the full class namespace and stack trace, as those get very long.
|
||||
*/
|
||||
protected function formatException(\Throwable $ex): string
|
||||
{
|
||||
$classname = get_class($ex);
|
||||
if ($pos = strrpos($classname, '\\')) {
|
||||
$classname = substr($classname, $pos + 1);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'- %s: %s, in file %s:%s',
|
||||
$classname,
|
||||
$ex->getMessage(),
|
||||
$ex->getFile(),
|
||||
$ex->getLine(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Writer;
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Log\LogLevel;
|
||||
use TYPO3\CMS\Core\Log\LogRecord;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Log writer that writes the log records into a database table.
|
||||
*/
|
||||
class DatabaseWriter extends AbstractWriter
|
||||
{
|
||||
/**
|
||||
* Writes the log record
|
||||
*
|
||||
* @param LogRecord $record Log record
|
||||
* @return \TYPO3\CMS\Core\Log\Writer\WriterInterface $this
|
||||
*/
|
||||
public function writeLog(LogRecord $record)
|
||||
{
|
||||
try {
|
||||
// Avoid ConnectionPool usage prior boot completion (see #96291).
|
||||
if (!GeneralUtility::getContainer()->get('boot.state')->complete) {
|
||||
return $this;
|
||||
}
|
||||
} catch (\LogicException $e) {
|
||||
// LogicException will be thrown if the container isn't available yet.
|
||||
return $this;
|
||||
}
|
||||
|
||||
$data = '';
|
||||
$context = $record->getData();
|
||||
if (!empty($context)) {
|
||||
// Fold an exception into the message, and string-ify it into context so it can be jsonified.
|
||||
if (isset($context['exception']) && $context['exception'] instanceof \Throwable) {
|
||||
$context['exception'] = (string)$context['exception'];
|
||||
}
|
||||
$data = json_encode($context);
|
||||
}
|
||||
|
||||
$fieldValues = [
|
||||
'request_id' => $record->getRequestId(),
|
||||
'time_micro' => $record->getCreated(),
|
||||
'component' => $record->getComponent(),
|
||||
'level' => LogLevel::normalizeLevel($record->getLevel()),
|
||||
'message' => $record->getMessage(),
|
||||
'data' => $data,
|
||||
];
|
||||
|
||||
// sys_log uses tstamp for garbage collection via TableGarbageCollectionTask.
|
||||
// Without it, tstamp defaults to 0 (1970-01-01), causing immediate deletion (see #109290).
|
||||
$fieldValues['tstamp'] = (int)$record->getCreated();
|
||||
|
||||
GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable('sys_log')
|
||||
->insert('sys_log', $fieldValues);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Writer\Enum;
|
||||
|
||||
enum Interval: string
|
||||
{
|
||||
case DAILY = 'daily';
|
||||
case WEEKLY = 'weekly';
|
||||
case MONTHLY = 'monthly';
|
||||
case YEARLY = 'yearly';
|
||||
|
||||
public function getDateInterval(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::DAILY => 'P1D',
|
||||
self::WEEKLY => 'P1W',
|
||||
self::MONTHLY => 'P1M',
|
||||
self::YEARLY => 'P1Y',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Writer;
|
||||
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Crypto\HashService;
|
||||
use TYPO3\CMS\Core\Log\Exception\InvalidLogWriterConfigurationException;
|
||||
use TYPO3\CMS\Core\Log\LogRecord;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
|
||||
/**
|
||||
* Log writer that writes the log records into a file.
|
||||
*/
|
||||
class FileWriter extends AbstractWriter
|
||||
{
|
||||
/**
|
||||
* Log file path, relative to TYPO3's base project folder
|
||||
*/
|
||||
protected string $logFile = '';
|
||||
|
||||
protected string $logFileInfix = '';
|
||||
|
||||
/**
|
||||
* Default log file path
|
||||
*/
|
||||
protected string $defaultLogFileTemplate = '/log/typo3_%s.log';
|
||||
|
||||
/**
|
||||
* Log file handle storage
|
||||
*
|
||||
* To avoid concurrent file handles on a the same file when using several FileWriter instances,
|
||||
* we share the file handles in a static class variable
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
protected static array $logFileHandles = [];
|
||||
|
||||
/**
|
||||
* Keep track of used file handles by different fileWriter instances
|
||||
*
|
||||
* As the logger gets instantiated by class name but the resources
|
||||
* are shared via the static $logFileHandles we need to track usage
|
||||
* of file handles to avoid closing handles that are still needed
|
||||
* by different instances. Only if the count is zero may the file
|
||||
* handle be closed.
|
||||
*/
|
||||
protected static array $logFileHandlesCount = [];
|
||||
|
||||
/**
|
||||
* Constructor, opens the log file handle
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
// the parent constructor reads $options and sets them
|
||||
parent::__construct($options);
|
||||
if (empty($options['logFile'])
|
||||
// omit logging if TYPO3 has not been configured (avoid creating a guessable filename)
|
||||
&& ($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] ?? '') !== ''
|
||||
) {
|
||||
$this->setLogFile($this->getDefaultLogFileName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor, closes the log file handle
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->logFile === '') {
|
||||
return;
|
||||
}
|
||||
self::$logFileHandlesCount[$this->logFile]--;
|
||||
if (self::$logFileHandlesCount[$this->logFile] <= 0) {
|
||||
$this->closeLogFile();
|
||||
}
|
||||
}
|
||||
|
||||
public function setLogFileInfix(string $infix)
|
||||
{
|
||||
$this->logFileInfix = $infix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path to the log file.
|
||||
*
|
||||
* @param string $relativeLogFile path to the log file, relative to public web dir
|
||||
* @return WriterInterface
|
||||
* @throws InvalidLogWriterConfigurationException
|
||||
*/
|
||||
public function setLogFile(string $relativeLogFile)
|
||||
{
|
||||
$logFile = $relativeLogFile;
|
||||
if (!PathUtility::isAbsolutePath($logFile)) {
|
||||
$logFile = GeneralUtility::getFileAbsFileName($logFile);
|
||||
if (empty($logFile)) {
|
||||
throw new InvalidLogWriterConfigurationException(
|
||||
'Log file path "' . $relativeLogFile . '" is not valid!',
|
||||
1444374805
|
||||
);
|
||||
}
|
||||
}
|
||||
$this->logFile = $logFile;
|
||||
$this->openLogFile();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path to the log file.
|
||||
*/
|
||||
public function getLogFile(): string
|
||||
{
|
||||
return $this->logFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the log record
|
||||
*
|
||||
* @param LogRecord $record Log record
|
||||
* @return WriterInterface $this
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function writeLog(LogRecord $record)
|
||||
{
|
||||
if ($this->logFile === '') {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$data = '';
|
||||
$context = $record->getData();
|
||||
$message = $record->getMessage();
|
||||
if (!empty($context)) {
|
||||
// Fold an exception into the message, and string-ify it into context so it can be jsonified.
|
||||
if (isset($context['exception']) && $context['exception'] instanceof \Throwable) {
|
||||
$message .= $this->formatException($context['exception']);
|
||||
$context['exception'] = (string)$context['exception'];
|
||||
}
|
||||
$data = '- ' . json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
$message = sprintf(
|
||||
'%s [%s] request="%s" component="%s": %s %s',
|
||||
date('r', (int)$record->getCreated()),
|
||||
strtoupper($record->getLevel()),
|
||||
$record->getRequestId(),
|
||||
$record->getComponent(),
|
||||
$this->interpolate($message, $context),
|
||||
$data
|
||||
);
|
||||
|
||||
if (fwrite(self::$logFileHandles[$this->logFile], $message . LF) === false) {
|
||||
throw new \RuntimeException('Could not write log record to log file', 1345036335);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the log file handle
|
||||
*
|
||||
* @throws \RuntimeException if the log file can't be opened.
|
||||
*/
|
||||
protected function openLogFile()
|
||||
{
|
||||
if (isset(self::$logFileHandlesCount[$this->logFile])) {
|
||||
self::$logFileHandlesCount[$this->logFile]++;
|
||||
} else {
|
||||
self::$logFileHandlesCount[$this->logFile] = 1;
|
||||
}
|
||||
if (isset(self::$logFileHandles[$this->logFile]) && is_resource(self::$logFileHandles[$this->logFile] ?? false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->createLogFile();
|
||||
self::$logFileHandles[$this->logFile] = fopen($this->logFile, 'a');
|
||||
if (!is_resource(self::$logFileHandles[$this->logFile])) {
|
||||
throw new \RuntimeException('Could not open log file "' . $this->logFile . '"', 1321804422);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the log file handle.
|
||||
*/
|
||||
protected function closeLogFile()
|
||||
{
|
||||
if (!empty(self::$logFileHandles[$this->logFile]) && is_resource(self::$logFileHandles[$this->logFile])) {
|
||||
fclose(self::$logFileHandles[$this->logFile]);
|
||||
unset(self::$logFileHandles[$this->logFile]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the log file with correct permissions
|
||||
* and parent directories, if needed
|
||||
*/
|
||||
protected function createLogFile()
|
||||
{
|
||||
if (file_exists($this->logFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// skip mkdir if logFile refers to any scheme but file:// or empty
|
||||
$scheme = parse_url($this->logFile, PHP_URL_SCHEME);
|
||||
if ($scheme === null || $scheme === 'file' || PathUtility::isAbsolutePath($this->logFile)) {
|
||||
// remove file:/ before creating the directory
|
||||
$logFileDirectory = PathUtility::dirname((string)preg_replace('#^file:/#', '', $this->logFile));
|
||||
if (!@is_dir($logFileDirectory)) {
|
||||
GeneralUtility::mkdir_deep($logFileDirectory);
|
||||
// create .htaccess file if log file is within the site path
|
||||
if (PathUtility::getCommonPrefix([Environment::getPublicPath() . '/', $logFileDirectory]) === (Environment::getPublicPath() . '/')) {
|
||||
// only create .htaccess, if we created the directory on our own
|
||||
$this->createHtaccessFile($logFileDirectory . '/.htaccess');
|
||||
}
|
||||
}
|
||||
}
|
||||
// create the log file
|
||||
GeneralUtility::writeFile($this->logFile, '', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates .htaccess file inside a new directory to access protect it
|
||||
*
|
||||
* @param string $htaccessFile Path of .htaccess file
|
||||
*/
|
||||
protected function createHtaccessFile($htaccessFile)
|
||||
{
|
||||
// write .htaccess file to protect the log file
|
||||
if (!empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['generateApacheHtaccess']) && !file_exists($htaccessFile)) {
|
||||
$htaccessContent = <<<END
|
||||
# Apache < 2.3
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
Satisfy All
|
||||
</IfModule>
|
||||
|
||||
# Apache ≥ 2.3
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
END;
|
||||
GeneralUtility::writeFile($htaccessFile, $htaccessContent, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the default log file.
|
||||
* Uses the defaultLogFileTemplate and replaces the %s placeholder with a short MD5 hash
|
||||
* based on a static string and the current encryption key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultLogFileName()
|
||||
{
|
||||
$hashService = GeneralUtility::makeInstance(HashService::class);
|
||||
$namePart = substr($hashService->hmac($this->defaultLogFileTemplate, 'defaultLogFile'), 0, 10);
|
||||
if ($this->logFileInfix !== '') {
|
||||
$namePart = $this->logFileInfix . '_' . $namePart;
|
||||
}
|
||||
return Environment::getVarPath() . sprintf($this->defaultLogFileTemplate, $namePart);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Writer;
|
||||
|
||||
use TYPO3\CMS\Core\Log\LogRecord;
|
||||
|
||||
/**
|
||||
* Null writer - just forgets about everything
|
||||
*/
|
||||
class NullWriter extends AbstractWriter
|
||||
{
|
||||
/**
|
||||
* Writes the log record
|
||||
*
|
||||
* @param LogRecord $record Log record
|
||||
* @return \TYPO3\CMS\Core\Log\Writer\WriterInterface $this
|
||||
*/
|
||||
public function writeLog(LogRecord $record)
|
||||
{
|
||||
// do nothing
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Writer;
|
||||
|
||||
use TYPO3\CMS\Core\Log\LogRecord;
|
||||
|
||||
/**
|
||||
* Log writer that writes the log records into PHP error log.
|
||||
*/
|
||||
class PhpErrorLogWriter extends AbstractWriter
|
||||
{
|
||||
/**
|
||||
* Writes the log record
|
||||
*
|
||||
* @param LogRecord $record Log record
|
||||
* @return \TYPO3\CMS\Core\Log\Writer\WriterInterface $this
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function writeLog(LogRecord $record)
|
||||
{
|
||||
$data = '';
|
||||
$context = $record->getData();
|
||||
$message = $record->getMessage();
|
||||
if (!empty($context)) {
|
||||
// Fold an exception into the message, and string-ify it into context so it can be jsonified.
|
||||
if (isset($context['exception']) && $context['exception'] instanceof \Throwable) {
|
||||
$message .= $this->formatException($context['exception']);
|
||||
$context['exception'] = (string)$context['exception'];
|
||||
}
|
||||
$data = '- ' . json_encode($context);
|
||||
}
|
||||
|
||||
$message = sprintf(
|
||||
'TYPO3 [%s] request="%s" component="%s": %s %s',
|
||||
strtoupper($record->getLevel()),
|
||||
$record->getRequestId(),
|
||||
$record->getComponent(),
|
||||
$this->interpolate($message, $context),
|
||||
$data
|
||||
);
|
||||
if (error_log($message) === false) {
|
||||
throw new \RuntimeException('Could not write log record to PHP error log', 1345036336);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Writer;
|
||||
|
||||
use TYPO3\CMS\Core\Locking\Exception\LockAcquireException;
|
||||
use TYPO3\CMS\Core\Locking\Exception\LockAcquireWouldBlockException;
|
||||
use TYPO3\CMS\Core\Locking\Exception\LockCreateException;
|
||||
use TYPO3\CMS\Core\Locking\LockFactory;
|
||||
use TYPO3\CMS\Core\Log\LogRecord;
|
||||
use TYPO3\CMS\Core\Log\Writer\Enum\Interval;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Write logs into files while providing basic rotation capabilities. This is a very basic approach, suitable for
|
||||
* environments where established tools like logrotate are not available.
|
||||
*/
|
||||
class RotatingFileWriter extends FileWriter
|
||||
{
|
||||
private const string ROTATION_DATE_FORMAT = 'YmdHis';
|
||||
|
||||
private Interval $interval = Interval::DAILY;
|
||||
private int $maxFiles = 5;
|
||||
private \DateTimeImmutable $lastRotation;
|
||||
private \DateTimeImmutable $nextRotation;
|
||||
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
parent::__construct($options);
|
||||
}
|
||||
|
||||
public function setLogFile(string $relativeLogFile): self
|
||||
{
|
||||
parent::setLogFile($relativeLogFile);
|
||||
|
||||
$this->updateRuntimeRotationState($this->getLastRotation());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal setter called by FileWriter constructor
|
||||
*/
|
||||
protected function setInterval(string|Interval $interval): void
|
||||
{
|
||||
if (is_string($interval)) {
|
||||
// String support is required for use in system/settings.php
|
||||
$this->interval = Interval::tryFrom($interval) ?? Interval::DAILY;
|
||||
} else {
|
||||
$this->interval = $interval;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal setter called by FileWriter constructor
|
||||
*/
|
||||
protected function setMaxFiles(int $maxFiles): void
|
||||
{
|
||||
$this->maxFiles = max(0, $maxFiles);
|
||||
}
|
||||
|
||||
public function writeLog(LogRecord $record)
|
||||
{
|
||||
if ($this->needsRotation()) {
|
||||
$lockFactory = GeneralUtility::makeInstance(LockFactory::class);
|
||||
try {
|
||||
$lock = $lockFactory->createLocker('rotate-' . $this->logFile);
|
||||
if ($lock->acquire()) {
|
||||
$this->updateRuntimeRotationState($this->getLastRotation());
|
||||
// check again if rotation is still needed (could have happened in the meantime)
|
||||
if ($this->needsRotation()) {
|
||||
$this->rotate();
|
||||
}
|
||||
}
|
||||
$lock->release();
|
||||
} catch (LockCreateException|LockAcquireException|LockAcquireWouldBlockException) {
|
||||
}
|
||||
}
|
||||
return parent::writeLog($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method rotates all log files found by using `glob()` to take all already rotated logs into account, even
|
||||
* after a configuration change.
|
||||
*
|
||||
* Log files are rotated using the "copytruncate" approach: the current open log file is copied as-is to a new
|
||||
* location, the current log file gets flushed afterward. This way, file handles don't need to get re-created.
|
||||
*/
|
||||
protected function rotate(): void
|
||||
{
|
||||
$rotationSuffix = date(self::ROTATION_DATE_FORMAT);
|
||||
|
||||
// copytruncate: Rotate the currently used log file
|
||||
copy($this->logFile, $this->logFile . '.' . $rotationSuffix);
|
||||
ftruncate(self::$logFileHandles[$this->logFile], 0);
|
||||
|
||||
$rotatedLogFiles = glob($this->logFile . '.*');
|
||||
rsort($rotatedLogFiles, SORT_NATURAL);
|
||||
|
||||
// Remove any excess files
|
||||
if ($this->maxFiles > 0) {
|
||||
$excessFiles = array_slice($rotatedLogFiles, $this->maxFiles);
|
||||
foreach ($excessFiles as $excessFile) {
|
||||
unlink($excessFile);
|
||||
}
|
||||
}
|
||||
|
||||
$this->updateRuntimeRotationState(new \DateTimeImmutable());
|
||||
}
|
||||
|
||||
protected function getLastRotation(): \DateTimeImmutable
|
||||
{
|
||||
// Rotate already rotated files again
|
||||
$rotatedLogFiles = glob($this->logFile . '.*');
|
||||
|
||||
if ($rotatedLogFiles !== []) {
|
||||
// Sort rotated files to handle the newest one first
|
||||
rsort($rotatedLogFiles, SORT_NATURAL);
|
||||
$newestLog = current($rotatedLogFiles);
|
||||
|
||||
$rotationDelimiterPosition = strrpos($newestLog, '.');
|
||||
$timestamp = substr($newestLog, $rotationDelimiterPosition + 1);
|
||||
|
||||
$latestRotationDateTime = \DateTimeImmutable::createFromFormat(self::ROTATION_DATE_FORMAT, $timestamp);
|
||||
if ($latestRotationDateTime instanceof \DateTimeImmutable) {
|
||||
return $latestRotationDateTime;
|
||||
}
|
||||
}
|
||||
|
||||
return new \DateTimeImmutable('@0');
|
||||
}
|
||||
|
||||
protected function determineNextRotation(): \DateTimeImmutable
|
||||
{
|
||||
return $this->lastRotation->add(new \DateInterval($this->interval->getDateInterval()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if log files need to be rotated under following conditions:
|
||||
*
|
||||
* 1.
|
||||
* a) either the next rotation is due
|
||||
* b) logs were never rotated before
|
||||
* 2. the log file is not empty - FileWriter::setLogFile() creates one if missing
|
||||
*/
|
||||
protected function needsRotation(): bool
|
||||
{
|
||||
return ($this->nextRotation <= new \DateTimeImmutable() || $this->lastRotation->getTimestamp() === 0) && filesize($this->logFile) > 0;
|
||||
}
|
||||
|
||||
protected function updateRuntimeRotationState(\DateTimeImmutable $lastRotation): void
|
||||
{
|
||||
$this->lastRotation = $lastRotation;
|
||||
$this->nextRotation = $this->determineNextRotation();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Writer;
|
||||
|
||||
use TYPO3\CMS\Core\Log\LogLevel;
|
||||
use TYPO3\CMS\Core\Log\LogRecord;
|
||||
|
||||
/**
|
||||
* Log writer that writes to syslog
|
||||
*/
|
||||
class SyslogWriter extends AbstractWriter
|
||||
{
|
||||
/**
|
||||
* List of valid syslog facility names.
|
||||
* private as it's not supposed to be changed.
|
||||
*
|
||||
* @var array<string, int> Facilities
|
||||
*/
|
||||
private $facilities = [
|
||||
'auth' => LOG_AUTH,
|
||||
'authpriv' => LOG_AUTHPRIV,
|
||||
'cron' => LOG_CRON,
|
||||
'daemon' => LOG_DAEMON,
|
||||
'kern' => LOG_KERN,
|
||||
'lpr' => LOG_LPR,
|
||||
'mail' => LOG_MAIL,
|
||||
'news' => LOG_NEWS,
|
||||
'syslog' => LOG_SYSLOG,
|
||||
'user' => LOG_USER,
|
||||
'uucp' => LOG_UUCP,
|
||||
];
|
||||
|
||||
/**
|
||||
* Type of program that is logging to syslog.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $facility = LOG_USER;
|
||||
|
||||
/**
|
||||
* Constructor, adds facilities on *nix environments.
|
||||
*
|
||||
* @param array $options Configuration options
|
||||
* @throws \RuntimeException if connection to syslog cannot be opened
|
||||
* @see \TYPO3\CMS\Core\Log\Writer\AbstractWriter
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
// additional facilities for *nix environments
|
||||
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
|
||||
$this->facilities['local0'] = LOG_LOCAL0;
|
||||
$this->facilities['local1'] = LOG_LOCAL1;
|
||||
$this->facilities['local2'] = LOG_LOCAL2;
|
||||
$this->facilities['local3'] = LOG_LOCAL3;
|
||||
$this->facilities['local4'] = LOG_LOCAL4;
|
||||
$this->facilities['local5'] = LOG_LOCAL5;
|
||||
$this->facilities['local6'] = LOG_LOCAL6;
|
||||
$this->facilities['local7'] = LOG_LOCAL7;
|
||||
}
|
||||
parent::__construct($options);
|
||||
openlog('TYPO3', LOG_ODELAY | LOG_PID, $this->facility);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor, closes connection to syslog.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
closelog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the facility to use when logging to syslog.
|
||||
*
|
||||
* @param string $facility Facility to use when logging.
|
||||
*/
|
||||
public function setFacility($facility): void
|
||||
{
|
||||
if (array_key_exists(strtolower($facility), $this->facilities)) {
|
||||
$this->facility = $this->facilities[strtolower($facility)];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data of the record in syslog format
|
||||
*/
|
||||
public function getMessageForSyslog(LogRecord $record): string
|
||||
{
|
||||
$data = '';
|
||||
$context = $record->getData();
|
||||
$message = $record->getMessage();
|
||||
if (!empty($context)) {
|
||||
// Fold an exception into the message, and string-ify it into context so it can be jsonified.
|
||||
if (isset($context['exception']) && $context['exception'] instanceof \Throwable) {
|
||||
$message .= $this->formatException($context['exception']);
|
||||
$context['exception'] = (string)$context['exception'];
|
||||
}
|
||||
$data = '- ' . json_encode($context);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'[request="%s" component="%s"] %s %s',
|
||||
$record->getRequestId(),
|
||||
$record->getComponent(),
|
||||
$this->interpolate($message, $context),
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the log record to syslog
|
||||
*
|
||||
* @return \TYPO3\CMS\Core\Log\Writer\WriterInterface
|
||||
*/
|
||||
public function writeLog(LogRecord $record)
|
||||
{
|
||||
syslog(LogLevel::normalizeLevel($record->getLevel()), $this->getMessageForSyslog($record));
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Log\Writer;
|
||||
|
||||
use TYPO3\CMS\Core\Log\LogRecord;
|
||||
|
||||
/**
|
||||
* Log writer interface
|
||||
*/
|
||||
interface WriterInterface
|
||||
{
|
||||
/**
|
||||
* Writes the log record
|
||||
*
|
||||
* @param \TYPO3\CMS\Core\Log\LogRecord $record Log record
|
||||
* @return \TYPO3\CMS\Core\Log\Writer\WriterInterface $this
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function writeLog(LogRecord $record);
|
||||
}
|
||||
Reference in New Issue
Block a user