Commit 3fca1b19 authored by crell's avatar crell Committed by Christian Kuhn
Browse files

[BUGFIX] Make logger usage PSR-3 compliant

PSR-3 has specific rules around interpolation: Messages
may provide placeholders like {foo} and writers should
substitute these in the messages if a context array with
such a key is provided.
Let's use placeholders correctly.

Resolves: #94315
Related: #94356
Releases: master
Change-Id: I2c285e84f1832c80828861369e99af9aff6cd267
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/69425

Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
parent 8ada892a
......@@ -117,7 +117,7 @@ class PasswordReset implements LoggerAwareInterface
return;
}
if ($this->hasExceededMaximumAttemptsForReset($context, $emailAddress)) {
$this->logger->alert('Password reset requested for email "' . $emailAddress . '" . but was requested too many times.');
$this->logger->alert('Password reset requested for email {email} but was requested too many times.', ['email' => $emailAddress]);
return;
}
$queryBuilder = $this->getPreparedQueryBuilder();
......@@ -159,7 +159,7 @@ class PasswordReset implements LoggerAwareInterface
->setTemplate('PasswordReset/AmbiguousResetRequested');
GeneralUtility::makeInstance(Mailer::class)->send($emailObject);
$this->logger->warning('Password reset sent to email address ' . $emailAddress . ' but multiple accounts found');
$this->logger->warning('Password reset sent to email address {email} but multiple accounts found', ['email' => $emailAddress]);
$this->log(
'Sent password reset email to email address %s but with multiple accounts attached.',
SystemLogLoginAction::PASSWORD_RESET_REQUEST,
......@@ -195,7 +195,10 @@ class PasswordReset implements LoggerAwareInterface
->setTemplate('PasswordReset/ResetRequested');
GeneralUtility::makeInstance(Mailer::class)->send($emailObject);
$this->logger->info('Sent password reset email to email address ' . $emailAddress . ' for user ' . $user['username']);
$this->logger->info('Sent password reset email to email address {email} for user {username}', [
'email' => $emailAddress,
'username' => $user['username'],
]);
$this->log(
'Sent password reset email to email address %s',
SystemLogLoginAction::PASSWORD_RESET_REQUEST,
......@@ -339,7 +342,7 @@ class PasswordReset implements LoggerAwareInterface
->getConnectionForTable('be_users')
->update('be_users', ['password_reset_token' => '', 'password' => $this->getHasher()->getHashedPassword($newPassword)], ['uid' => $userId]);
$this->logger->info('Password reset successful for user ' . $userId);
$this->logger->info('Password reset successful for user {user_id)', ['user_id' => $userId]);
$this->log(
'Password reset successful for user %s',
SystemLogLoginAction::PASSWORD_RESET_ACCOMPLISHED,
......
......@@ -58,7 +58,7 @@ class ImageProcessController implements LoggerAwareInterface
);
} catch (\Throwable $e) {
// Fatal error occurred, which will be responded as 404
$this->logger->error(sprintf('Processing of file with id "%s" failed', $processedFileId), ['exception' => $e]);
$this->logger->error('Processing of file with id {processed_file} failed', ['processed_file' => $processedFileId, 'exception' => $e]);
}
return new HtmlResponse('', 404);
......
......@@ -321,12 +321,11 @@ class StandardContentPreviewRenderer implements PreviewRendererInterface, Logger
}
return $view->render();
} catch (\Exception $e) {
$this->logger->warning(sprintf(
'The backend preview for content element %d can not be rendered using the Fluid template file "%s": %s',
$row['uid'],
$fluidTemplateFile,
$e->getMessage()
));
$this->logger->warning('The backend preview for content element {uid} can not be rendered using the Fluid template file "{file}"', [
'uid' => $row['uid'],
'file' => $fluidTemplateFile,
'exception' => $e,
]);
if ($GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] && $this->getBackendUser()->isAdmin()) {
$view = GeneralUtility::makeInstance(StandaloneView::class);
......
......@@ -103,7 +103,7 @@ class EmailLoginNotification implements LoggerAwareInterface
* @param AbstractUserAuthentication $user
* @param string|null $subjectPrefix
*/
protected function sendEmail(string $recipient, AbstractUserAuthentication $user, string $subjectPrefix = null): void
protected function sendEmail(string $recipient, AbstractUserAuthentication $user, ?string $subjectPrefix = null): void
{
$headline = 'TYPO3 Backend Login notification';
$recipients = explode(',', $recipient);
......@@ -120,16 +120,18 @@ class EmailLoginNotification implements LoggerAwareInterface
try {
GeneralUtility::makeInstance(Mailer::class)->send($email);
} catch (TransportException $e) {
$this->logger->warning('Could not send notification email to "' . $recipient . '" due to mailer settings error', [
$this->logger->warning('Could not send notification email to "{recipient}" due to mailer settings error', [
'recipient' => $recipient,
'userId' => $user->user['uid'] ?? 0,
'recipientList' => $recipients,
'exception' => $e
'exception' => $e,
]);
} catch (RfcComplianceException $e) {
$this->logger->warning('Could not send notification email to "' . $recipient . '" due to invalid email address', [
$this->logger->warning('Could not send notification email to "{recipient}" due to invalid email address', [
'recipient' => $recipient,
'userId' => $user->user['uid'] ?? 0,
'recipientList' => $recipients,
'exception' => $e
'exception' => $e,
]);
}
}
......
......@@ -987,7 +987,12 @@ class BackendUtility
* The storage does not exist anymore
* Log the exception message for admins as they maybe can restore the storage
*/
self::getLogger()->error($e->getMessage(), ['table' => $tableName, 'fieldName' => $fieldName, 'referenceUid' => $referenceUid, 'exception' => $e]);
self::getLogger()->error($e->getMessage(), [
'table' => $tableName,
'fieldName' => $fieldName,
'referenceUid' => $referenceUid,
'exception' => $e,
]);
}
}
......
......@@ -48,10 +48,9 @@ class AuthenticationStyleInformation implements LoggerAwareInterface
$backgroundImageUri = $this->getUriForFileName($backgroundImage);
if ($backgroundImageUri === '') {
$this->logger->warning(
'The configured TYPO3 backend login background image "' . htmlspecialchars($backgroundImageUri) .
'" can\'t be resolved. Please check if the file exists and the extension is activated.'
);
$this->logger->warning('The configured TYPO3 backend login background image "{image_url}" can\'t be resolved. Please check if the file exists and the extension is activated.', [
'image_url' => $backgroundImageUri,
]);
return '';
}
......@@ -103,10 +102,9 @@ class AuthenticationStyleInformation implements LoggerAwareInterface
}
$logoUri = $this->getUriForFileName($logo);
if ($logoUri === '') {
$this->logger->warning(
'The configured TYPO3 backend login logo "' . htmlspecialchars($logoUri) .
'" can\'t be resolved. Please check if the file exists and the extension is activated.'
);
$this->logger->warning('The configured TYPO3 backend login logo "{logo_url}" can\'t be resolved. Please check if the file exists and the extension is activated.', [
'logo_url' => $logoUri,
]);
return '';
}
......
......@@ -1287,12 +1287,11 @@ class PageLayoutView implements LoggerAwareInterface
}
return $view->render();
} catch (\Exception $e) {
$this->logger->warning(sprintf(
'The backend preview for content element %d can not be rendered using the Fluid template file "%s": %s',
$row['uid'],
$fluidTemplateFile,
$e->getMessage()
));
$this->logger->warning('The backend preview for content element {uid} can not be rendered using the Fluid template file "{file}"', [
'uid' => $row['uid'],
'file' => $fluidTemplateFile,
$e->getMessage(),
]);
if ($GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] && $this->getBackendUser()->isAdmin()) {
$view = GeneralUtility::makeInstance(StandaloneView::class);
......
......@@ -18,6 +18,7 @@ declare(strict_types=1);
namespace TYPO3\CMS\Backend\Tests\Functional\Authentication;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerTrait;
use TYPO3\CMS\Backend\Authentication\PasswordReset;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Http\ServerRequest;
......@@ -25,6 +26,25 @@ use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
class PasswordResetTest extends FunctionalTestCase
{
protected LoggerInterface $logger;
public function setUp(): void
{
parent::setUp();
$this->logger = new class() implements LoggerInterface {
use LoggerTrait;
public array $records = [];
public function log($level, $message, array $context = []): void
{
$this->records[] = [
'level' => $level,
'message' => $message,
'context' => $context
];
}
};
}
/**
* @test
*/
......@@ -130,12 +150,12 @@ class PasswordResetTest extends FunctionalTestCase
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport'] = 'null';
$emailAddress = 'duplicate@example.com';
$subject = new PasswordReset();
$loggerProphecy = $this->prophesize(LoggerInterface::class);
$loggerProphecy->warning()->withArguments(['Password reset sent to email address ' . $emailAddress . ' but multiple accounts found'])->shouldBeCalled();
$subject->setLogger($loggerProphecy->reveal());
$subject->setLogger($this->logger);
$context = new Context();
$request = new ServerRequest();
$subject->initiateReset($request, $context, $emailAddress);
self::assertEquals('warning', $this->logger->records[0]['level']);
self::assertEquals($emailAddress, $this->logger->records[0]['context']['email']);
}
/**
......@@ -150,12 +170,13 @@ class PasswordResetTest extends FunctionalTestCase
$emailAddress = 'editor-with-email@example.com';
$username = 'editor-with-email';
$subject = new PasswordReset();
$loggerProphecy = $this->prophesize(LoggerInterface::class);
$loggerProphecy->info()->withArguments(['Sent password reset email to email address ' . $emailAddress . ' for user ' . $username])->shouldBeCalled();
$subject->setLogger($loggerProphecy->reveal());
$subject->setLogger($this->logger);
$context = new Context();
$request = new ServerRequest();
$subject->initiateReset($request, $context, $emailAddress);
self::assertEquals('info', $this->logger->records[0]['level']);
self::assertEquals($emailAddress, $this->logger->records[0]['context']['email']);
self::assertEquals($username, $this->logger->records[0]['context']['username']);
}
/**
......
......@@ -332,10 +332,11 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
false,
$cookieSameSite
);
$this->logger->debug(
($isRefreshTimeBasedCookie ? 'Updated Cookie: ' : 'Set Cookie: ')
. $sessionId . ($cookieDomain ? ', ' . $cookieDomain : '')
);
$message = $isRefreshTimeBasedCookie ? 'Updated Cookie: {session}, {domain}' : 'Set Cookie: {session}, {domain}';
$this->logger->debug($message, [
'session' => $sessionId,
'domain' => $cookieDomain,
]);
}
}
......@@ -358,7 +359,7 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
$match = [];
$matchCnt = @preg_match($cookieDomain, GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'), $match);
if ($matchCnt === false) {
$this->logger->critical('The regular expression for the cookie domain (' . $cookieDomain . ') contains errors. The session is not shared across sub-domains.');
$this->logger->critical('The regular expression for the cookie domain ({domain}) contains errors. The session is not shared across sub-domains.', ['domain' => $cookieDomain]);
} elseif ($matchCnt) {
$result = $match[0];
}
......@@ -424,7 +425,7 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
{
$authConfiguration = $this->getAuthServiceConfiguration();
if (!empty($authConfiguration)) {
$this->logger->debug('Authentication Service Configuration found.', $authConfiguration);
$this->logger->debug('Authentication Service Configuration found.', ['auth_configuration' => $authConfiguration]);
}
// No user for now - will be searched by service below
$tempuserArr = [];
......@@ -433,7 +434,7 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
$authenticated = false;
// User want to login with passed login data (name/password)
$activeLogin = false;
$this->logger->debug('Login type: ' . $this->loginType);
$this->logger->debug('Login type: {type}', ['type' => $this->loginType]);
// The info array provide additional information for auth services
$authInfo = $this->getAuthInfoArray();
// Get Login/Logout data submitted by a form or params
......@@ -445,7 +446,7 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
// $type,$action,$error,$details_nr,$details,$data,$tablename,$recuid,$recpid
$this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::LOGOUT, SystemLogErrorClassification::MESSAGE, 2, 'User %s logged out', [$this->user['username']], '', 0, 0);
}
$this->logger->info('User logged out. Id: ' . $this->userSession->getIdentifier());
$this->logger->info('User logged out. Id: {session}', ['session' => $this->userSession->getIdentifier()]);
$this->logoff();
}
// Determine whether we need to skip session update.
......@@ -528,7 +529,7 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
if (empty($tempuserArr)) {
$this->logger->debug('No user found by services');
} else {
$this->logger->debug(count($tempuserArr) . ' user records found by services');
$this->logger->debug('{count} user records found by services', ['count' => count($tempuserArr)]);
}
}
......@@ -625,9 +626,15 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
if ($this->writeStdLog) {
$this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::LOGIN, SystemLogErrorClassification::MESSAGE, 1, 'User %s logged in from ###IP###', [$tempuser[$this->username_column]], '', '', '');
}
$this->logger->info('User ' . $tempuser[$this->username_column] . ' logged in from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR'));
$this->logger->info('User {username} logged in from {ip}', [
'username' => $tempuser[$this->username_column],
'ip' => GeneralUtility::getIndpEnv('REMOTE_ADDR'),
]);
} else {
$this->logger->debug('User ' . $tempuser[$this->username_column] . ' authenticated from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR'));
$this->logger->debug('User {username} authenticated from {ip}', [
'username' => $tempuser[$this->username_column],
'ip' => GeneralUtility::getIndpEnv('REMOTE_ADDR'),
]);
}
} else {
// Mark the current login attempt as failed
......@@ -725,7 +732,10 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
yield $serviceObj;
}
if (!empty($serviceChain)) {
$this->logger->debug($subType . ' auth services called: ' . implode(', ', $serviceChain));
$this->logger->debug('{subtype} auth services called: {chain}', [
'subtype' => $subType,
'chain' => $serviceChain,
]);
}
}
......@@ -859,7 +869,7 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
*/
public function logoff()
{
$this->logger->debug('logoff: ses_id = ' . $this->userSession->getIdentifier());
$this->logger->debug('logoff: ses_id = {session}', ['session' => $this->userSession->getIdentifier()]);
$_params = [];
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'] ?? [] as $_funcRef) {
......@@ -982,7 +992,10 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
if (!is_array($variable)) {
$variable = $this->uc;
}
$this->logger->debug('writeUC: ' . $this->userid_column . '=' . (int)$this->user[$this->userid_column]);
$this->logger->debug('writeUC: {userid_column}={value}', [
'userid_column' => $this->userid_column,
'value' => $this->user[$this->userid_column],
]);
GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table)->update(
$this->user_table,
['uc' => serialize($variable)],
......@@ -1089,7 +1102,7 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
public function setAndSaveSessionData($key, $data)
{
$this->userSession->set($key, $data);
$this->logger->debug('setAndSaveSessionData: ses_id = ' . $this->userSession->getIdentifier());
$this->logger->debug('setAndSaveSessionData: ses_id = {session}', ['session' => $this->userSession->getIdentifier()]);
$this->userSession = $this->userSessionManager->updateSession($this->userSession);
}
......
......@@ -63,7 +63,10 @@ class AuthenticationService extends AbstractAuthenticationService
$this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::ATTEMPT, SystemLogErrorClassification::SECURITY_NOTICE, 2, 'Login-attempt from ###IP### for username \'%s\' with an empty password!', [
$this->login['uname']
]);
$this->logger->warning(sprintf('Login-attempt from %s, for username \'%s\' with an empty password!', $this->authInfo['REMOTE_ADDR'], $this->login['uname']));
$this->logger->warning('Login-attempt from {ip}, for username "{username}" with an empty password!', [
'ip' => $this->authInfo['REMOTE_ADDR'],
'username' => $this->login['uname'],
]);
return false;
}
......@@ -71,8 +74,9 @@ class AuthenticationService extends AbstractAuthenticationService
if (!is_array($user)) {
// Failed login attempt (no username found)
$this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::ATTEMPT, SystemLogErrorClassification::SECURITY_NOTICE, 2, 'Login-attempt from ###IP###, username \'%s\' not found!', [$this->login['uname']]);
$this->logger->info('Login-attempt from username \'' . $this->login['uname'] . '\' not found!', [
'REMOTE_ADDR' => $this->authInfo['REMOTE_ADDR']
$this->logger->info('Login-attempt from username "{username}" not found!', [
'username' => $this->login['uname'],
'REMOTE_ADDR' => $this->authInfo['REMOTE_ADDR'],
]);
} else {
$this->logger->debug('User found', [
......@@ -184,7 +188,10 @@ class AuthenticationService extends AbstractAuthenticationService
['password' => $newPassword],
['uid' => $uid]
);
$this->logger->notice('Automatic password update for user record in ' . $table . ' with uid ' . $uid);
$this->logger->notice('Automatic password update for user record in {table} with uid {uid}', [
'table' => $table,
'uid' => $uid,
]);
}
/**
......
......@@ -5960,7 +5960,7 @@ class DataHandler implements LoggerAwareInterface
$this->remapListedDBRecords_procInline($conf, $value, $uid, $table);
break;
default:
$this->logger->debug('Field type should not appear here: ' . $conf['type']);
$this->logger->debug('Field type should not appear here: {type}', ['type' => $conf['type']]);
}
}
// If any fields were changed, those fields are updated!
......
......@@ -104,8 +104,8 @@ class Connection extends \Doctrine\DBAL\Connection implements LoggerAwareInterfa
}
foreach ($this->prepareConnectionCommands as $command) {
if ($this->executeUpdate($command) === false) {
$this->logger->critical('Could not initialize DB connection with query "' . $command . '": ' . $this->errorInfo());
if ($this->executeStatement($command) === false) {
$this->logger->critical('Could not initialize DB connection with query: {query}', ['query' => $command]);
}
}
......
......@@ -928,10 +928,10 @@ class ReferenceIndex implements LoggerAwareInterface
->execute();
} catch (DBALException $e) {
// Table exists in TCA but does not exist in the database
$msg = 'Table "' .
$tableName .
'" exists in TCA but does not exist in the database. You should run the Database Analyzer in the Install Tool to fix this.';
$this->logger->error($msg, ['exception' => $e]);
$this->logger->error('Table {table_name} exists in TCA but does not exist in the database. You should run the Database Analyzer in the Install Tool to fix this.', [
'table_name' => $tableName,
'exception' => $e,
]);
continue;
}
......@@ -964,7 +964,7 @@ class ReferenceIndex implements LoggerAwareInterface
// Subselect based queries only work on the same connection
if ($refIndexConnectionName !== $tableConnectionName) {
$this->logger->error('Not checking table "' . $tableName . '" for lost indexes, "sys_refindex" table uses a different connection');
$this->logger->error('Not checking table {table_name} for lost indexes, "sys_refindex" table uses a different connection', ['table_name' => $tableName]);
continue;
}
......
......@@ -1010,15 +1010,14 @@ class PageRepository implements LoggerAwareInterface
}
}
// Check if short cut page was a shortcut itself, if so look up recursively:
if ($page['doktype'] == self::DOKTYPE_SHORTCUT) {
if ((int)$page['doktype'] === self::DOKTYPE_SHORTCUT) {
if (!in_array($page['uid'], $pageLog) && $iteration > 0) {
$pageLog[] = $page['uid'];
$page = $this->getPageShortcut($page['shortcut'], $page['shortcut_mode'], $page['uid'], $iteration - 1, $pageLog, $disableGroupCheck);
} else {
$pageLog[] = $page['uid'];
$message = 'Page shortcuts were looping in uids ' . implode(',', $pageLog) . '...!';
$this->logger->error($message);
throw new \RuntimeException($message, 1294587212);
$this->logger->error('Page shortcuts were looping in uids {uids}', ['uids' => implode(', ', array_values($pageLog))]);
throw new \RuntimeException('Page shortcuts were looping in uids: ' . implode(', ', array_values($pageLog)), 1294587212);
}
}
// Return resulting page:
......
......@@ -67,35 +67,55 @@ abstract class AbstractExceptionHandler implements ExceptionHandlerInterface, Si
* Writes exception to different logs
*
* @param \Throwable $exception The throwable object.
* @param string $context The context where the exception was thrown, WEB or CLI
* @param string $mode The context where the exception was thrown.
* Either self::CONTEXT_WEB or self::CONTEXT_CLI.
*/
protected function writeLogEntries(\Throwable $exception, $context)
protected function writeLogEntries(\Throwable $exception, string $mode): void
{
// Do not write any logs for some messages to avoid filling up tables or files with illegal requests
if (in_array($exception->getCode(), self::IGNORED_EXCEPTION_CODES, true)) {
return;
}
// PSR-3 logging framework.
try {
if ($this->logger) {
// 'FE' if in FrontendApplication, else 'BE' (also in CLI without request object)
$applicationMode = ($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
&& ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend()
? 'FE'
: 'BE';
$requestUrl = $this->anonymizeToken(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'));
$this->logger->critical('Core: Exception handler ({mode}: {application_mode}): {exception_class}, code #{exception_code}, file {file}, line {line}: {message}', [
'mode' => $mode,
'application_mode' => $applicationMode,
'exception_class' => get_class($exception),
'exception_code' => $exception->getCode(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'message' => $exception->getMessage(),
'request_url' => $requestUrl,
'exception' => $exception,
]);
}
} catch (\Exception $exception) {
// A nested exception here was probably caused by a database failure, which means there's little
// else that can be done other than moving on and letting the system hard-fail.
}
// Legacy logger. Remove this section eventually.
$filePathAndName = $exception->getFile();
$exceptionCodeNumber = $exception->getCode() > 0 ? '#' . $exception->getCode() . ': ' : '';
$logTitle = 'Core: Exception handler (' . $context . ')';
$logTitle = 'Core: Exception handler (' . $mode . ')';
$logMessage = 'Uncaught TYPO3 Exception: ' . $exceptionCodeNumber . $exception->getMessage() . ' | '
. get_class($exception) . ' thrown in file ' . $filePathAndName . ' in line ' . $exception->getLine();
if ($context === 'WEB') {
if ($mode === self::CONTEXT_WEB) {
$logMessage .= '. Requested URL: ' . $this->anonymizeToken(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'));
}
// When database credentials are wrong, the exception is probably
// caused by this. Therefor we cannot do any database operation,
// caused by this. Therefore we cannot do any database operation,
// otherwise this will lead into recurring exceptions.
try {
if ($this->logger) {
// 'FE' if in FrontendApplication, else 'BE' (also in CLI without request object)
$applicationType = ($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
&& ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend() ? 'FE' : 'BE';
$this->logger->critical($logTitle . ': ' . $logMessage, [
'TYPO3_MODE' => $applicationType,
'exception' => $exception
]);
}
// Write error message to sys_log table
$this->writeLog($logTitle . ': ' . $logMessage);
} catch (\Exception $exception) {
......
......@@ -150,7 +150,11 @@ class LogManager implements SingletonInterface, LogManagerInterface
$logWriter = GeneralUtility::makeInstance($logWriterClassName, $logWriterOptions);
$logger->addWriter($severityLevel, $logWriter);
} catch (InvalidArgumentException|InvalidLogWriterConfigurationException $e) {
$logger->warning('Instantiation of LogWriter "' . $logWriterClassName . '" failed for logger ' . $logger->getName() . ' (' . $e->getMessage() . ')');
$logger->warning('Instantiation of LogWriter "{class_name}" failed for logger {name}', [
'class_name' => $logWriterClassName,
'name' => $logger->getName(),
'exception' => $e,
]);
}
}
}
......@@ -171,7 +175,11 @@ class LogManager implements SingletonInterface, LogManagerInterface
$logProcessor = GeneralUtility::makeInstance($logProcessorClassName, $logProcessorOptions);
$logger->addProcessor($severityLevel, $logProcessor);
} catch (InvalidArgumentException|InvalidLogProcessorConfigurationException $e) {
$logger->warning('Instantiation of LogProcessor "' . $logProcessorClassName . '" failed for logger ' . $logger->getName() . ' (' . $e->getMessage() . ')');
$logger->warning('Instantiation of LogProcessor "{class_name}" failed for logger {name}', [
'class_name' => $logProcessorClassName,
'name' => $logger->getName(),
'exception' => $e,
]);
}
}
}
......
......@@ -42,4 +42,60 @@ abstract class AbstractWriter implements WriterInterface
}
}
}
/**
* 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'))) {