‪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 
23 
28 {
32  protected string ‪$logFile = '';
33 
34  protected string ‪$logFileInfix = '';
35 
39  protected string ‪$defaultLogFileTemplate = '/log/typo3_%s.log';
40 
49  protected static array ‪$logFileHandles = [];
50 
60  protected static array ‪$logFileHandlesCount = [];
61 
65  public function ‪__construct(array $options = [])
66  {
67  // the parent constructor reads $options and sets them
68  parent::__construct($options);
69  if (empty($options['logFile']) &&
70  // omit logging if TYPO3 has not been configured (avoid creating a guessable filename)
71  (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] ?? '') !== ''
72  ) {
73  $this->‪setLogFile($this->‪getDefaultLogFileName());
74  }
75  }
76 
80  public function ‪__destruct()
81  {
82  if ($this->logFile === '') {
83  return;
84  }
85  self::$logFileHandlesCount[‪$this->logFile]--;
86  if (self::$logFileHandlesCount[$this->logFile] <= 0) {
87  $this->‪closeLogFile();
88  }
89  }
90 
91  public function ‪setLogFileInfix(string $infix)
92  {
93  $this->logFileInfix = $infix;
94  }
95 
103  public function ‪setLogFile(string $relativeLogFile)
104  {
105  ‪$logFile = $relativeLogFile;
106  // Skip handling if logFile is a stream resource. This is used by unit tests with vfs:// directories
108  ‪$logFile = GeneralUtility::getFileAbsFileName(‪$logFile);
109  if (empty(‪$logFile)) {
111  'Log file path "' . $relativeLogFile . '" is not valid!',
112  1444374805
113  );
114  }
115  }
116  $this->logFile = ‪$logFile;
117  $this->‪openLogFile();
118 
119  return $this;
120  }
121 
125  public function ‪getLogFile(): string
126  {
127  return ‪$this->logFile;
128  }
129 
138  {
139  if ($this->logFile === '') {
140  return $this;
141  }
142 
143  $data = '';
144  $context = ‪$record->getData();
145  $message = ‪$record->getMessage();
146  if (!empty($context)) {
147  // Fold an exception into the message, and string-ify it into context so it can be jsonified.
148  if (isset($context['exception']) && $context['exception'] instanceof \Throwable) {
149  $message .= $this->‪formatException($context['exception']);
150  $context['exception'] = (string)$context['exception'];
151  }
152  $data = '- ' . json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
153  }
154 
155  $message = sprintf(
156  '%s [%s] request="%s" component="%s": %s %s',
157  date('r', (int)‪$record->getCreated()),
158  strtoupper(‪$record->getLevel()),
159  ‪$record->getRequestId(),
160  ‪$record->getComponent(),
161  $this->interpolate($message, $context),
162  $data
163  );
164 
165  if (fwrite(self::$logFileHandles[$this->logFile], $message . LF) === false) {
166  throw new \RuntimeException('Could not write log record to log file', 1345036335);
167  }
168 
169  return $this;
170  }
171 
177  protected function ‪openLogFile()
178  {
179  if (isset(self::$logFileHandlesCount[$this->logFile])) {
180  self::$logFileHandlesCount[‪$this->logFile]++;
181  } else {
182  self::$logFileHandlesCount[‪$this->logFile] = 1;
183  }
184  if (isset(self::$logFileHandles[$this->logFile]) && is_resource(self::$logFileHandles[$this->logFile] ?? false)) {
185  return;
186  }
187 
188  $this->‪createLogFile();
189  self::$logFileHandles[‪$this->logFile] = fopen($this->logFile, 'a');
190  if (!is_resource(self::$logFileHandles[$this->logFile])) {
191  throw new \RuntimeException('Could not open log file "' . $this->logFile . '"', 1321804422);
192  }
193  }
194 
198  protected function ‪closeLogFile()
199  {
200  if (!empty(self::$logFileHandles[$this->logFile]) && is_resource(self::$logFileHandles[$this->logFile])) {
201  fclose(self::$logFileHandles[$this->logFile]);
202  unset(self::$logFileHandles[$this->logFile]);
203  }
204  }
205 
210  protected function ‪createLogFile()
211  {
212  if (file_exists($this->logFile)) {
213  return;
214  }
215 
216  // skip mkdir if logFile refers to any scheme but vfs://, file:// or empty
217  $scheme = parse_url($this->logFile, PHP_URL_SCHEME);
218  if ($scheme === null || $scheme === 'file' || $scheme === 'vfs' || ‪PathUtility::isAbsolutePath($this->logFile)) {
219  // remove file:/ before creating the directory
220  $logFileDirectory = ‪PathUtility::dirname((string)preg_replace('#^file:/#', '', $this->logFile));
221  if (!@is_dir($logFileDirectory)) {
222  ‪GeneralUtility::mkdir_deep($logFileDirectory);
223  // create .htaccess file if log file is within the site path
224  if (‪PathUtility::getCommonPrefix([‪Environment::getPublicPath() . '/', $logFileDirectory]) === (‪Environment::getPublicPath() . '/')) {
225  // only create .htaccess, if we created the directory on our own
226  $this->‪createHtaccessFile($logFileDirectory . '/.htaccess');
227  }
228  }
229  }
230  // create the log file
231  ‪GeneralUtility::writeFile($this->logFile, '');
232  }
233 
239  protected function ‪createHtaccessFile($htaccessFile)
240  {
241  // write .htaccess file to protect the log file
242  if (!empty(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['generateApacheHtaccess']) && !file_exists($htaccessFile)) {
243  $htaccessContent = <<<END
244 # Apache < 2.3
245 <IfModule !mod_authz_core.c>
246  Order allow,deny
247  Deny from all
248  Satisfy All
249 </IfModule>
250 
251 # Apache ≥ 2.3
252 <IfModule mod_authz_core.c>
253  Require all denied
254 </IfModule>
255 END;
256  ‪GeneralUtility::writeFile($htaccessFile, $htaccessContent);
257  }
258  }
259 
267  protected function ‪getDefaultLogFileName()
268  {
269  $namePart = substr(‪GeneralUtility::hmac($this->defaultLogFileTemplate, 'defaultLogFile'), 0, 10);
270  if ($this->logFileInfix !== '') {
271  $namePart = $this->logFileInfix . '_' . $namePart;
272  }
273  return ‪Environment::getVarPath() . sprintf($this->defaultLogFileTemplate, $namePart);
274  }
275 }
‪TYPO3\CMS\Core\Log\Writer\FileWriter\setLogFileInfix
‪setLogFileInfix(string $infix)
Definition: FileWriter.php:91
‪TYPO3\CMS\Core\Log\Writer\FileWriter\$logFileInfix
‪string $logFileInfix
Definition: FileWriter.php:34
‪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:80
‪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:39
‪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:1649
‪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:1464
‪TYPO3\CMS\Core\Log\Writer\FileWriter\getLogFile
‪getLogFile()
Definition: FileWriter.php:125
‪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:210
‪TYPO3\CMS\Core\Log\Writer\FileWriter\getDefaultLogFileName
‪string getDefaultLogFileName()
Definition: FileWriter.php:267
‪TYPO3\CMS\Core\Utility\GeneralUtility\hmac
‪static string hmac($input, $additionalSecret='')
Definition: GeneralUtility.php:474
‪TYPO3\CMS\Core\Log\Writer\FileWriter
Definition: FileWriter.php:28
‪TYPO3\CMS\Core\Log\Writer
Definition: AbstractWriter.php:16
‪TYPO3\CMS\Core\Log\Writer\FileWriter\$logFile
‪string $logFile
Definition: FileWriter.php:32
‪TYPO3\CMS\Core\Log\Writer\FileWriter\$logFileHandles
‪static array $logFileHandles
Definition: FileWriter.php:49
‪TYPO3\CMS\Core\Log\Writer\AbstractWriter
Definition: AbstractWriter.php:25
‪TYPO3\CMS\Core\Log\Writer\FileWriter\openLogFile
‪openLogFile()
Definition: FileWriter.php:177
‪$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:239
‪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:60
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Log\Writer\FileWriter\__construct
‪__construct(array $options=[])
Definition: FileWriter.php:65
‪TYPO3\CMS\Core\Log\Writer\FileWriter\setLogFile
‪WriterInterface setLogFile(string $relativeLogFile)
Definition: FileWriter.php:103
‪TYPO3\CMS\Core\Log\Writer\FileWriter\closeLogFile
‪closeLogFile()
Definition: FileWriter.php:198
‪TYPO3\CMS\Core\Log\Writer\FileWriter\writeLog
‪WriterInterface writeLog(LogRecord $record)
Definition: FileWriter.php:137