8e7a4d9583e74b1dcd2b786d130eecd056ca2c01
[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
22 /**
23 * Log writer that writes the log records into a file.
24 */
25 class FileWriter extends AbstractWriter {
26
27 /**
28 * Log file path, relative to PATH_site
29 *
30 * @var string
31 */
32 protected $logFile = '';
33
34 /**
35 * Default log file path
36 *
37 * @var string
38 */
39 protected $defaultLogFile = 'typo3temp/logs/typo3.log';
40
41 /**
42 * Log file handle storage
43 *
44 * To avoid concurrent file handles on a the same file when using several FileWriter instances,
45 * we share the file handles in a static class variable
46 *
47 * @static
48 * @var array
49 */
50 static protected $logFileHandles = array();
51
52 /**
53 * Constructor, opens the log file handle
54 *
55 * @param array $options
56 * @return FileWriter
57 */
58 public function __construct(array $options = array()) {
59 // the parent constructor reads $options and sets them
60 parent::__construct($options);
61 if (empty($options['logFile'])) {
62 $this->setLogFile($this->defaultLogFile);
63 }
64 }
65
66 /**
67 * Destructor, closes the log file handle
68 */
69 public function __destruct() {
70 $this->closeLogFile();
71 }
72
73 /**
74 * Sets the path to the log file.
75 *
76 * @param string $logFile path to the log file, relative to PATH_site
77 * @return WriterInterface
78 * @throws InvalidLogWriterConfigurationException
79 */
80 public function setLogFile($logFile) {
81
82 // Skip handling if logFile is a stream resource. This is used by unit tests with vfs:// directories
83 if (FALSE === strpos($logFile, '://')) {
84 if (!GeneralUtility::isAllowedAbsPath((PATH_site . $logFile))) {
85 throw new InvalidLogWriterConfigurationException('Log file path "' . $logFile . '" is not valid!', 1326411176);
86 }
87 $logFile = GeneralUtility::getFileAbsFileName($logFile);
88 }
89 $this->logFile = $logFile;
90 $this->openLogFile();
91
92 return $this;
93 }
94
95 /**
96 * Gets the path to the log file.
97 *
98 * @return string Path to the log file.
99 */
100 public function getLogFile() {
101 return $this->logFile;
102 }
103
104 /**
105 * Writes the log record
106 *
107 * @param LogRecord $record Log record
108 * @return WriterInterface $this
109 * @throws \RuntimeException
110 */
111 public function writeLog(LogRecord $record) {
112 $timestamp = date('r', (int)$record->getCreated());
113 $levelName = LogLevel::getName($record->getLevel());
114 $data = '';
115 $recordData = $record->getData();
116 if (!empty($recordData)) {
117 // According to PSR3 the exception-key may hold an \Exception
118 // Since json_encode() does not encode an exception, we run the _toString() here
119 if (isset($recordData['exception']) && $recordData['exception'] instanceof \Exception) {
120 $recordData['exception'] = (string)$recordData['exception'];
121 }
122 $data = '- ' . json_encode($recordData);
123 }
124
125 $message = sprintf(
126 '%s [%s] request="%s" component="%s": %s %s',
127 $timestamp,
128 $levelName,
129 $record->getRequestId(),
130 $record->getComponent(),
131 $record->getMessage(),
132 $data
133 );
134
135 if (FALSE === fwrite(self::$logFileHandles[$this->logFile], $message . LF)) {
136 throw new \RuntimeException('Could not write log record to log file', 1345036335);
137 }
138
139 return $this;
140 }
141
142 /**
143 * Opens the log file handle
144 *
145 * @return void
146 * @throws \RuntimeException if the log file can't be opened.
147 */
148 protected function openLogFile() {
149 if (is_resource(self::$logFileHandles[$this->logFile])) {
150 return;
151 }
152
153 $this->createLogFile();
154 self::$logFileHandles[$this->logFile] = fopen($this->logFile, 'a');
155 if (!is_resource(self::$logFileHandles[$this->logFile])) {
156 throw new \RuntimeException('Could not open log file "' . $this->logFile . '"', 1321804422);
157 }
158 }
159
160 /**
161 * Closes the log file handle.
162 *
163 * @return void
164 */
165 protected function closeLogFile() {
166 if (is_resource(self::$logFileHandles[$this->logFile])) {
167 fclose(self::$logFileHandles[$this->logFile]);
168 unset(self::$logFileHandles[$this->logFile]);
169 }
170 }
171
172 /**
173 * Creates the log file with correct permissions
174 * and parent directories, if needed
175 *
176 * @return void
177 */
178 protected function createLogFile() {
179 if (file_exists($this->logFile)) {
180 return;
181 }
182 $logFileDirectory = dirname($this->logFile);
183 if (!@is_dir($logFileDirectory)) {
184 GeneralUtility::mkdir_deep($logFileDirectory);
185 // only create .htaccess, if we created the directory on our own
186 $this->createHtaccessFile($logFileDirectory . '/.htaccess');
187 }
188 // create the log file
189 GeneralUtility::writeFile($this->logFile, '');
190 }
191
192 /**
193 * Creates .htaccess file inside a new directory to access protect it
194 *
195 * @param string $htaccessFile Path of .htaccess file
196 * @return void
197 */
198 protected function createHtaccessFile($htaccessFile) {
199 // write .htaccess file to protect the log file
200 if (!empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['generateApacheHtaccess']) && !file_exists($htaccessFile)) {
201 $htaccessContent = '
202 # Apache < 2.3
203 <IfModule !mod_authz_core.c>
204 Order allow,deny
205 Deny from all
206 Satisfy All
207 </IfModule>
208
209 # Apache ≥ 2.3
210 <IfModule mod_authz_core.c>
211 Require all denied
212 </IfModule>
213 ';
214 GeneralUtility::writeFile($htaccessFile, $htaccessContent);
215 }
216 }
217
218 }