TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:09 +02:00
commit af8cc155b5
6818 changed files with 642608 additions and 0 deletions
+97
View File
@@ -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(),
);
}
}
+75
View File
@@ -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;
}
}
+36
View File
@@ -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',
};
}
}
+276
View File
@@ -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);
}
}
+36
View File
@@ -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;
}
}
+59
View File
@@ -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;
}
}
+170
View File
@@ -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();
}
}
+133
View File
@@ -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;
}
}
+33
View File
@@ -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);
}