‪TYPO3CMS  ‪main
FileWriter.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
24 
29 {
33  protected string ‪$logFile = '';
34 
35  protected string ‪$logFileInfix = '';
36 
40  protected string ‪$defaultLogFileTemplate = '/log/typo3_%s.log';
41 
50  protected static array ‪$logFileHandles = [];
51 
61  protected static array ‪$logFileHandlesCount = [];
62 
66  public function ‪__construct(array $options = [])
67  {
68  // the parent constructor reads $options and sets them
69  parent::__construct($options);
70  if (empty($options['logFile']) &&
71  // omit logging if TYPO3 has not been configured (avoid creating a guessable filename)
72  (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] ?? '') !== ''
73  ) {
74  $this->‪setLogFile($this->‪getDefaultLogFileName());
75  }
76  }
77 
81  public function ‪__destruct()
82  {
83  if ($this->logFile === '') {
84  return;
85  }
86  self::$logFileHandlesCount[‪$this->logFile]--;
87  if (self::$logFileHandlesCount[$this->logFile] <= 0) {
88  $this->‪closeLogFile();
89  }
90  }
91 
92  public function ‪setLogFileInfix(string $infix)
93  {
94  $this->logFileInfix = $infix;
95  }
96 
104  public function ‪setLogFile(string $relativeLogFile)
105  {
106  ‪$logFile = $relativeLogFile;
107  // Skip handling if logFile is a stream resource. This is used by unit tests with vfs:// directories
109  ‪$logFile = GeneralUtility::getFileAbsFileName(‪$logFile);
110  if (empty(‪$logFile)) {
112  'Log file path "' . $relativeLogFile . '" is not valid!',
113  1444374805
114  );
115  }
116  }
117  $this->logFile = ‪$logFile;
118  $this->‪openLogFile();
119 
120  return $this;
121  }
122 
126  public function ‪getLogFile(): string
127  {
128  return ‪$this->logFile;
129  }
130 
139  {
140  if ($this->logFile === '') {
141  return $this;
142  }
143 
144  $data = '';
145  $context = ‪$record->getData();
146  $message = ‪$record->getMessage();
147  if (!empty($context)) {
148  // Fold an exception into the message, and string-ify it into context so it can be jsonified.
149  if (isset($context['exception']) && $context['exception'] instanceof \Throwable) {
150  $message .= $this->‪formatException($context['exception']);
151  $context['exception'] = (string)$context['exception'];
152  }
153  $data = '- ' . json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
154  }
155 
156  $message = sprintf(
157  '%s [%s] request="%s" component="%s": %s %s',
158  date('r', (int)‪$record->getCreated()),
159  strtoupper(‪$record->getLevel()),
160  ‪$record->getRequestId(),
161  ‪$record->getComponent(),
162  $this->interpolate($message, $context),
163  $data
164  );
165 
166  if (fwrite(self::$logFileHandles[$this->logFile], $message . LF) === false) {
167  throw new \RuntimeException('Could not write log record to log file', 1345036335);
168  }
169 
170  return $this;
171  }
172 
178  protected function ‪openLogFile()
179  {
180  if (isset(self::$logFileHandlesCount[$this->logFile])) {
181  self::$logFileHandlesCount[‪$this->logFile]++;
182  } else {
183  self::$logFileHandlesCount[‪$this->logFile] = 1;
184  }
185  if (isset(self::$logFileHandles[$this->logFile]) && is_resource(self::$logFileHandles[$this->logFile] ?? false)) {
186  return;
187  }
188 
189  $this->‪createLogFile();
190  self::$logFileHandles[‪$this->logFile] = fopen($this->logFile, 'a');
191  if (!is_resource(self::$logFileHandles[$this->logFile])) {
192  throw new \RuntimeException('Could not open log file "' . $this->logFile . '"', 1321804422);
193  }
194  }
195 
199  protected function ‪closeLogFile()
200  {
201  if (!empty(self::$logFileHandles[$this->logFile]) && is_resource(self::$logFileHandles[$this->logFile])) {
202  fclose(self::$logFileHandles[$this->logFile]);
203  unset(self::$logFileHandles[$this->logFile]);
204  }
205  }
206 
211  protected function ‪createLogFile()
212  {
213  if (file_exists($this->logFile)) {
214  return;
215  }
216 
217  // skip mkdir if logFile refers to any scheme but vfs://, file:// or empty
218  $scheme = parse_url($this->logFile, PHP_URL_SCHEME);
219  if ($scheme === null || $scheme === 'file' || $scheme === 'vfs' || ‪PathUtility::isAbsolutePath($this->logFile)) {
220  // remove file:/ before creating the directory
221  $logFileDirectory = ‪PathUtility::dirname((string)preg_replace('#^file:/#', '', $this->logFile));
222  if (!@is_dir($logFileDirectory)) {
223  ‪GeneralUtility::mkdir_deep($logFileDirectory);
224  // create .htaccess file if log file is within the site path
225  if (‪PathUtility::getCommonPrefix([‪Environment::getPublicPath() . '/', $logFileDirectory]) === (‪Environment::getPublicPath() . '/')) {
226  // only create .htaccess, if we created the directory on our own
227  $this->‪createHtaccessFile($logFileDirectory . '/.htaccess');
228  }
229  }
230  }
231  // create the log file
232  ‪GeneralUtility::writeFile($this->logFile, '');
233  }
234 
240  protected function ‪createHtaccessFile($htaccessFile)
241  {
242  // write .htaccess file to protect the log file
243  if (!empty(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['generateApacheHtaccess']) && !file_exists($htaccessFile)) {
244  $htaccessContent = <<<END
245 # Apache < 2.3
246 <IfModule !mod_authz_core.c>
247  Order allow,deny
248  Deny from all
249  Satisfy All
250 </IfModule>
251 
252 # Apache ≥ 2.3
253 <IfModule mod_authz_core.c>
254  Require all denied
255 </IfModule>
256 END;
257  ‪GeneralUtility::writeFile($htaccessFile, $htaccessContent);
258  }
259  }
260 
268  protected function ‪getDefaultLogFileName()
269  {
270  $hashService = GeneralUtility::makeInstance(HashService::class);
271  $namePart = substr($hashService->hmac($this->defaultLogFileTemplate, 'defaultLogFile'), 0, 10);
272  if ($this->logFileInfix !== '') {
273  $namePart = $this->logFileInfix . '_' . $namePart;
274  }
275  return ‪Environment::getVarPath() . sprintf($this->defaultLogFileTemplate, $namePart);
276  }
277 }
‪TYPO3\CMS\Core\Log\Writer\FileWriter\setLogFileInfix
‪setLogFileInfix(string $infix)
Definition: FileWriter.php:92
‪TYPO3\CMS\Core\Log\Writer\FileWriter\$logFileInfix
‪string $logFileInfix
Definition: FileWriter.php:35
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪TYPO3\CMS\Core\Utility\PathUtility\isAbsolutePath
‪static isAbsolutePath(string $path)
Definition: PathUtility.php:286
‪TYPO3\CMS\Core\Log\Writer\FileWriter\__destruct
‪__destruct()
Definition: FileWriter.php:81
‪TYPO3\CMS\Core\Log\Exception\InvalidLogWriterConfigurationException
Definition: InvalidLogWriterConfigurationException.php:23
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static getPublicPath()
Definition: Environment.php:187
‪TYPO3\CMS\Core\Log\Writer\FileWriter\$defaultLogFileTemplate
‪string $defaultLogFileTemplate
Definition: FileWriter.php:40
‪TYPO3\CMS\Core\Utility\PathUtility\getCommonPrefix
‪static getCommonPrefix(array $paths)
Definition: PathUtility.php:165
‪TYPO3\CMS\Core\Utility\GeneralUtility\mkdir_deep
‪static mkdir_deep(string $directory)
Definition: GeneralUtility.php:1654
‪TYPO3\CMS\Core\Core\Environment\getVarPath
‪static getVarPath()
Definition: Environment.php:197
‪TYPO3\CMS\Core\Utility\PathUtility\dirname
‪static dirname(string $path)
Definition: PathUtility.php:243
‪TYPO3\CMS\Core\Utility\GeneralUtility\writeFile
‪static bool writeFile(string $file, string $content, bool $changePermissions=false)
Definition: GeneralUtility.php:1469
‪TYPO3\CMS\Core\Log\Writer\FileWriter\getLogFile
‪getLogFile()
Definition: FileWriter.php:126
‪TYPO3\CMS\Core\Log\LogRecord
Definition: LogRecord.php:24
‪TYPO3\CMS\Core\Log\Writer\WriterInterface
Definition: WriterInterface.php:24
‪TYPO3\CMS\Webhooks\Message\$record
‪identifier readonly int readonly array $record
Definition: PageModificationMessage.php:36
‪TYPO3\CMS\Core\Log\Writer\FileWriter\createLogFile
‪createLogFile()
Definition: FileWriter.php:211
‪TYPO3\CMS\Core\Log\Writer\FileWriter\getDefaultLogFileName
‪string getDefaultLogFileName()
Definition: FileWriter.php:268
‪TYPO3\CMS\Core\Log\Writer\FileWriter
Definition: FileWriter.php:29
‪TYPO3\CMS\Core\Log\Writer
Definition: AbstractWriter.php:16
‪TYPO3\CMS\Core\Log\Writer\FileWriter\$logFile
‪string $logFile
Definition: FileWriter.php:33
‪TYPO3\CMS\Core\Log\Writer\FileWriter\$logFileHandles
‪static array $logFileHandles
Definition: FileWriter.php:50
‪TYPO3\CMS\Core\Log\Writer\AbstractWriter
Definition: AbstractWriter.php:25
‪TYPO3\CMS\Core\Log\Writer\FileWriter\openLogFile
‪openLogFile()
Definition: FileWriter.php:178
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:41
‪TYPO3\CMS\Core\Log\Writer\AbstractWriter\formatException
‪formatException(\Throwable $ex)
Definition: AbstractWriter.php:82
‪TYPO3\CMS\Core\Log\Writer\FileWriter\createHtaccessFile
‪createHtaccessFile($htaccessFile)
Definition: FileWriter.php:240
‪TYPO3\CMS\Core\Utility\PathUtility\hasProtocolAndScheme
‪static hasProtocolAndScheme(string $path)
Definition: PathUtility.php:445
‪TYPO3\CMS\Core\Log\Writer\FileWriter\$logFileHandlesCount
‪static array $logFileHandlesCount
Definition: FileWriter.php:61
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Crypto\HashService
Definition: HashService.php:27
‪TYPO3\CMS\Core\Log\Writer\FileWriter\__construct
‪__construct(array $options=[])
Definition: FileWriter.php:66
‪TYPO3\CMS\Core\Log\Writer\FileWriter\setLogFile
‪WriterInterface setLogFile(string $relativeLogFile)
Definition: FileWriter.php:104
‪TYPO3\CMS\Core\Log\Writer\FileWriter\closeLogFile
‪closeLogFile()
Definition: FileWriter.php:199
‪TYPO3\CMS\Core\Log\Writer\FileWriter\writeLog
‪WriterInterface writeLog(LogRecord $record)
Definition: FileWriter.php:138