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