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 = << Order allow,deny Deny from all Satisfy All # Apache ≥ 2.3 Require all denied 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); } }