These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * @link http://www.yiiframework.com/ |
||
4 | * @copyright Copyright (c) 2008 Yii Software LLC |
||
5 | * @license http://www.yiiframework.com/license/ |
||
6 | */ |
||
7 | |||
8 | namespace yii\log; |
||
9 | |||
10 | use Yii; |
||
11 | use yii\base\InvalidConfigException; |
||
12 | use yii\helpers\FileHelper; |
||
13 | |||
14 | /** |
||
15 | * FileTarget records log messages in a file. |
||
16 | * |
||
17 | * The log file is specified via [[logFile]]. If the size of the log file exceeds |
||
18 | * [[maxFileSize]] (in kilo-bytes), a rotation will be performed, which renames |
||
19 | * the current log file by suffixing the file name with '.1'. All existing log |
||
20 | * files are moved backwards by one place, i.e., '.2' to '.3', '.1' to '.2', and so on. |
||
21 | * The property [[maxLogFiles]] specifies how many history files to keep. |
||
22 | * |
||
23 | * @author Qiang Xue <[email protected]> |
||
24 | * @since 2.0 |
||
25 | */ |
||
26 | class FileTarget extends Target |
||
27 | { |
||
28 | /** |
||
29 | * @var string log file path or [path alias](guide:concept-aliases). If not set, it will use the "@runtime/logs/app.log" file. |
||
30 | * The directory containing the log files will be automatically created if not existing. |
||
31 | */ |
||
32 | public $logFile; |
||
33 | /** |
||
34 | * @var bool whether log files should be rotated when they reach a certain [[maxFileSize|maximum size]]. |
||
35 | * Log rotation is enabled by default. This property allows you to disable it, when you have configured |
||
36 | * an external tools for log rotation on your server. |
||
37 | * @since 2.0.3 |
||
38 | */ |
||
39 | public $enableRotation = true; |
||
40 | /** |
||
41 | * @var int maximum log file size, in kilo-bytes. Defaults to 10240, meaning 10MB. |
||
42 | */ |
||
43 | public $maxFileSize = 10240; // in KB |
||
44 | /** |
||
45 | * @var int number of log files used for rotation. Defaults to 5. |
||
46 | */ |
||
47 | public $maxLogFiles = 5; |
||
48 | /** |
||
49 | * @var int the permission to be set for newly created log files. |
||
50 | * This value will be used by PHP chmod() function. No umask will be applied. |
||
51 | * If not set, the permission will be determined by the current environment. |
||
52 | */ |
||
53 | public $fileMode; |
||
54 | /** |
||
55 | * @var int the permission to be set for newly created directories. |
||
56 | * This value will be used by PHP chmod() function. No umask will be applied. |
||
57 | * Defaults to 0775, meaning the directory is read-writable by owner and group, |
||
58 | * but read-only for other users. |
||
59 | */ |
||
60 | public $dirMode = 0775; |
||
61 | /** |
||
62 | * @var bool Whether to rotate log files by copy and truncate in contrast to rotation by |
||
63 | * renaming files. Defaults to `true` to be more compatible with log tailers and is windows |
||
64 | * systems which do not play well with rename on open files. Rotation by renaming however is |
||
65 | * a bit faster. |
||
66 | * |
||
67 | * The problem with windows systems where the [rename()](http://www.php.net/manual/en/function.rename.php) |
||
68 | * function does not work with files that are opened by some process is described in a |
||
69 | * [comment by Martin Pelletier](http://www.php.net/manual/en/function.rename.php#102274) in |
||
70 | * the PHP documentation. By setting rotateByCopy to `true` you can work |
||
71 | * around this problem. |
||
72 | */ |
||
73 | public $rotateByCopy = true; |
||
74 | |||
75 | |||
76 | /** |
||
77 | * Initializes the route. |
||
78 | * This method is invoked after the route is created by the route manager. |
||
79 | */ |
||
80 | 2 | public function init() |
|
81 | { |
||
82 | 2 | parent::init(); |
|
83 | 2 | if ($this->logFile === null) { |
|
84 | $this->logFile = Yii::$app->getRuntimePath() . '/logs/app.log'; |
||
85 | } else { |
||
86 | 2 | $this->logFile = Yii::getAlias($this->logFile); |
|
87 | } |
||
88 | 2 | $logPath = dirname($this->logFile); |
|
89 | 2 | if (!is_dir($logPath)) { |
|
90 | FileHelper::createDirectory($logPath, $this->dirMode, true); |
||
91 | } |
||
92 | 2 | if ($this->maxLogFiles < 1) { |
|
93 | $this->maxLogFiles = 1; |
||
94 | } |
||
95 | 2 | if ($this->maxFileSize < 1) { |
|
96 | $this->maxFileSize = 1; |
||
97 | } |
||
98 | 2 | } |
|
99 | |||
100 | /** |
||
101 | * Writes log messages to a file. |
||
102 | * Starting from version 2.0.14, this method throws LogRuntimeException in case the log can not be exported. |
||
103 | * @throws InvalidConfigException if unable to open the log file for writing |
||
104 | * @throws LogRuntimeException if unable to write complete log to file |
||
105 | */ |
||
106 | 2 | public function export() |
|
107 | { |
||
108 | 2 | $text = implode("\n", array_map([$this, 'formatMessage'], $this->messages)) . "\n"; |
|
109 | 2 | if (($fp = @fopen($this->logFile, 'a')) === false) { |
|
110 | throw new InvalidConfigException("Unable to append to log file: {$this->logFile}"); |
||
111 | } |
||
112 | 2 | @flock($fp, LOCK_EX); |
|
0 ignored issues
–
show
|
|||
113 | 2 | if ($this->enableRotation) { |
|
114 | // clear stat cache to ensure getting the real current file size and not a cached one |
||
115 | // this may result in rotating twice when cached file size is used on subsequent calls |
||
116 | 2 | clearstatcache(); |
|
117 | } |
||
118 | 2 | if ($this->enableRotation && @filesize($this->logFile) > $this->maxFileSize * 1024) { |
|
119 | 2 | $this->rotateFiles(); |
|
120 | 2 | @flock($fp, LOCK_UN); |
|
121 | 2 | @fclose($fp); |
|
122 | 2 | $writeResult = @file_put_contents($this->logFile, $text, FILE_APPEND | LOCK_EX); |
|
123 | 2 | if ($writeResult === false) { |
|
124 | $error = error_get_last(); |
||
125 | throw new LogRuntimeException("Unable to export log through file!: {$error['message']}"); |
||
126 | } |
||
127 | 2 | $textSize = strlen($text); |
|
128 | 2 | if ($writeResult < $textSize) { |
|
129 | 2 | throw new LogRuntimeException("Unable to export whole log through file! Wrote $writeResult out of $textSize bytes."); |
|
130 | } |
||
131 | } else { |
||
132 | 2 | $writeResult = @fwrite($fp, $text); |
|
133 | 2 | if ($writeResult === false) { |
|
134 | $error = error_get_last(); |
||
135 | throw new LogRuntimeException("Unable to export log through file!: {$error['message']}"); |
||
136 | } |
||
137 | 2 | $textSize = strlen($text); |
|
138 | 2 | if ($writeResult < $textSize) { |
|
139 | throw new LogRuntimeException("Unable to export whole log through file! Wrote $writeResult out of $textSize bytes."); |
||
140 | } |
||
141 | 2 | @flock($fp, LOCK_UN); |
|
142 | 2 | @fclose($fp); |
|
143 | } |
||
144 | 2 | if ($this->fileMode !== null) { |
|
145 | @chmod($this->logFile, $this->fileMode); |
||
146 | } |
||
147 | 2 | } |
|
148 | |||
149 | /** |
||
150 | * Rotates log files. |
||
151 | */ |
||
152 | 2 | protected function rotateFiles() |
|
153 | { |
||
154 | 2 | $file = $this->logFile; |
|
155 | 2 | for ($i = $this->maxLogFiles; $i >= 0; --$i) { |
|
156 | // $i == 0 is the original log file |
||
157 | 2 | $rotateFile = $file . ($i === 0 ? '' : '.' . $i); |
|
158 | 2 | if (is_file($rotateFile)) { |
|
159 | // suppress errors because it's possible multiple processes enter into this section |
||
160 | 2 | if ($i === $this->maxLogFiles) { |
|
161 | 2 | @unlink($rotateFile); |
|
162 | 2 | continue; |
|
163 | } |
||
164 | 2 | $newFile = $this->logFile . '.' . ($i + 1); |
|
165 | 2 | $this->rotateByCopy ? $this->rotateByCopy($rotateFile, $newFile) : $this->rotateByRename($rotateFile, $newFile); |
|
166 | 2 | if ($i === 0) { |
|
167 | 2 | $this->clearLogFile($rotateFile); |
|
168 | } |
||
169 | } |
||
170 | } |
||
171 | 2 | } |
|
172 | |||
173 | /*** |
||
174 | * Clear log file without closing any other process open handles |
||
175 | * @param string $rotateFile |
||
176 | */ |
||
177 | 2 | private function clearLogFile($rotateFile) |
|
178 | { |
||
179 | 2 | if ($filePointer = @fopen($rotateFile, 'a')) { |
|
180 | 2 | @ftruncate($filePointer, 0); |
|
181 | 2 | @fclose($filePointer); |
|
182 | } |
||
183 | 2 | } |
|
184 | |||
185 | /*** |
||
186 | * Copy rotated file into new file |
||
187 | * @param string $rotateFile |
||
188 | * @param string $newFile |
||
189 | */ |
||
190 | 1 | private function rotateByCopy($rotateFile, $newFile) |
|
191 | { |
||
192 | 1 | @copy($rotateFile, $newFile); |
|
193 | 1 | if ($this->fileMode !== null) { |
|
194 | @chmod($newFile, $this->fileMode); |
||
195 | } |
||
196 | 1 | } |
|
197 | |||
198 | /** |
||
199 | * Renames rotated file into new file |
||
200 | * @param string $rotateFile |
||
201 | * @param string $newFile |
||
202 | */ |
||
203 | 1 | private function rotateByRename($rotateFile, $newFile) |
|
204 | { |
||
205 | 1 | @rename($rotateFile, $newFile); |
|
206 | 1 | } |
|
207 | } |
||
208 |
If you suppress an error, we recommend checking for the error condition explicitly: