[TASK] Fix CGL errors
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Log / Writer / FileWriter.php
1 <?php
2 namespace TYPO3\CMS\Core\Log\Writer;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Log\Exception\InvalidLogWriterConfigurationException;
18 use TYPO3\CMS\Core\Log\LogLevel;
19 use TYPO3\CMS\Core\Log\LogRecord;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Core\Utility\PathUtility;
22
23 /**
24 * Log writer that writes the log records into a file.
25 */
26 class FileWriter extends AbstractWriter
27 {
28 /**
29 * Log file path, relative to PATH_site
30 *
31 * @var string
32 */
33 protected $logFile = '';
34
35 /**
36 * Default log file path
37 *
38 * @var string
39 */
40 protected $defaultLogFileTemplate = 'typo3temp/logs/typo3_%s.log';
41
42 /**
43 * Log file handle storage
44 *
45 * To avoid concurrent file handles on a the same file when using several FileWriter instances,
46 * we share the file handles in a static class variable
47 *
48 * @static
49 * @var array
50 */
51 protected static $logFileHandles = array();
52
53 /**
54 * Constructor, opens the log file handle
55 *
56 * @param array $options
57 * @return FileWriter
58 */
59 public function __construct(array $options = array())
60 {
61 // the parent constructor reads $options and sets them
62 parent::__construct($options);
63 if (empty($options['logFile'])) {
64 $this->setLogFile($this->getDefaultLogFileName());
65 }
66 }
67
68 /**
69 * Destructor, closes the log file handle
70 */
71 public function __destruct()
72 {
73 $this->closeLogFile();
74 }
75
76 /**
77 * Sets the path to the log file.
78 *
79 * @param string $relativeLogFile path to the log file, relative to PATH_site
80 * @return WriterInterface
81 * @throws InvalidLogWriterConfigurationException
82 */
83 public function setLogFile($relativeLogFile)
84 {
85 $logFile = $relativeLogFile;
86 // Skip handling if logFile is a stream resource. This is used by unit tests with vfs:// directories
87 if (false === strpos($logFile, '://') && !PathUtility::isAbsolutePath($logFile)) {
88 $logFile = GeneralUtility::getFileAbsFileName($logFile);
89 if ($logFile === null) {
90 throw new InvalidLogWriterConfigurationException('Log file path "' . $relativeLogFile . '" is not valid!', 1444374805);
91 }
92 }
93 $this->logFile = $logFile;
94 $this->openLogFile();
95
96 return $this;
97 }
98
99 /**
100 * Gets the path to the log file.
101 *
102 * @return string Path to the log file.
103 */
104 public function getLogFile()
105 {
106 return $this->logFile;
107 }
108
109 /**
110 * Writes the log record
111 *
112 * @param LogRecord $record Log record
113 * @return WriterInterface $this
114 * @throws \RuntimeException
115 */
116 public function writeLog(LogRecord $record)
117 {
118 $timestamp = date('r', (int)$record->getCreated());
119 $levelName = LogLevel::getName($record->getLevel());
120 $data = '';
121 $recordData = $record->getData();
122 if (!empty($recordData)) {
123 // According to PSR3 the exception-key may hold an \Exception
124 // Since json_encode() does not encode an exception, we run the _toString() here
125 if (isset($recordData['exception']) && $recordData['exception'] instanceof \Exception) {
126 $recordData['exception'] = (string)$recordData['exception'];
127 }
128 $data = '- ' . json_encode($recordData);
129 }
130
131 $message = sprintf(
132 '%s [%s] request="%s" component="%s": %s %s',
133 $timestamp,
134 $levelName,
135 $record->getRequestId(),
136 $record->getComponent(),
137 $record->getMessage(),
138 $data
139 );
140
141 if (false === fwrite(self::$logFileHandles[$this->logFile], $message . LF)) {
142 throw new \RuntimeException('Could not write log record to log file', 1345036335);
143 }
144
145 return $this;
146 }
147
148 /**
149 * Opens the log file handle
150 *
151 * @return void
152 * @throws \RuntimeException if the log file can't be opened.
153 */
154 protected function openLogFile()
155 {
156 if (is_resource(self::$logFileHandles[$this->logFile])) {
157 return;
158 }
159
160 $this->createLogFile();
161 self::$logFileHandles[$this->logFile] = fopen($this->logFile, 'a');
162 if (!is_resource(self::$logFileHandles[$this->logFile])) {
163 throw new \RuntimeException('Could not open log file "' . $this->logFile . '"', 1321804422);
164 }
165 }
166
167 /**
168 * Closes the log file handle.
169 *
170 * @return void
171 */
172 protected function closeLogFile()
173 {
174 if (is_resource(self::$logFileHandles[$this->logFile])) {
175 fclose(self::$logFileHandles[$this->logFile]);
176 unset(self::$logFileHandles[$this->logFile]);
177 }
178 }
179
180 /**
181 * Creates the log file with correct permissions
182 * and parent directories, if needed
183 *
184 * @return void
185 */
186 protected function createLogFile()
187 {
188 if (file_exists($this->logFile)) {
189 return;
190 }
191 $logFileDirectory = dirname($this->logFile);
192 if (!@is_dir($logFileDirectory)) {
193 GeneralUtility::mkdir_deep($logFileDirectory);
194 // create .htaccess file if log file is within the site path
195 if (PathUtility::getCommonPrefix(array(PATH_site, $logFileDirectory)) === PATH_site) {
196 // only create .htaccess, if we created the directory on our own
197 $this->createHtaccessFile($logFileDirectory . '/.htaccess');
198 }
199 }
200 // create the log file
201 GeneralUtility::writeFile($this->logFile, '');
202 }
203
204 /**
205 * Creates .htaccess file inside a new directory to access protect it
206 *
207 * @param string $htaccessFile Path of .htaccess file
208 * @return void
209 */
210 protected function createHtaccessFile($htaccessFile)
211 {
212 // write .htaccess file to protect the log file
213 if (!empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['generateApacheHtaccess']) && !file_exists($htaccessFile)) {
214 $htaccessContent = '
215 # Apache < 2.3
216 <IfModule !mod_authz_core.c>
217 Order allow,deny
218 Deny from all
219 Satisfy All
220 </IfModule>
221
222 # Apache ≥ 2.3
223 <IfModule mod_authz_core.c>
224 Require all denied
225 </IfModule>
226 ';
227 GeneralUtility::writeFile($htaccessFile, $htaccessContent);
228 }
229 }
230
231 /**
232 * Returns the path to the default log file.
233 *
234 * Uses the defaultLogFileTemplate and replaces the %s placeholder with a short MD5 hash
235 * based on a static string and the current encryption key.
236 *
237 * @return string
238 */
239 protected function getDefaultLogFileName()
240 {
241 return sprintf($this->defaultLogFileTemplate, substr(GeneralUtility::hmac($this->defaultLogFileTemplate, 'defaultLogFile'), 0, 10));
242 }
243 }