1 <?php |
|
2 /** |
|
3 * Licensed to the Apache Software Foundation (ASF) under one or more |
|
4 * contributor license agreements. See the NOTICE file distributed with |
|
5 * this work for additional information regarding copyright ownership. |
|
6 * The ASF licenses this file to You under the Apache License, Version 2.0 |
|
7 * (the "License"); you may not use this file except in compliance with |
|
8 * the License. You may obtain a copy of the License at |
|
9 * |
|
10 * http://www.apache.org/licenses/LICENSE-2.0 |
|
11 * |
|
12 * Unless required by applicable law or agreed to in writing, software |
|
13 * distributed under the License is distributed on an "AS IS" BASIS, |
|
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
15 * See the License for the specific language governing permissions and |
|
16 * limitations under the License. |
|
17 * |
|
18 * @package log4php |
|
19 */ |
|
20 |
|
21 /** |
|
22 * LoggerAppenderRollingFile writes logging events to a specified file. The |
|
23 * file is rolled over after a specified size has been reached. |
|
24 * |
|
25 * This appender uses a layout. |
|
26 * |
|
27 * ## Configurable parameters: ## |
|
28 * |
|
29 * - **file** - Path to the target file. |
|
30 * - **append** - If set to true, the appender will append to the file, |
|
31 * otherwise the file contents will be overwritten. |
|
32 * - **maxBackupIndex** - Maximum number of backup files to keep. Default is 1. |
|
33 * - **maxFileSize** - Maximum allowed file size (in bytes) before rolling |
|
34 * over. Suffixes "KB", "MB" and "GB" are allowed. 10KB = 10240 bytes, etc. |
|
35 * Default is 10M. |
|
36 * - **compress** - If set to true, rolled-over files will be compressed. |
|
37 * Requires the zlib extension. |
|
38 * |
|
39 * @version $Revision: 1394975 $ |
|
40 * @package log4php |
|
41 * @subpackage appenders |
|
42 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 |
|
43 * @link http://logging.apache.org/log4php/docs/appenders/rolling-file.html Appender documentation |
|
44 */ |
|
45 class LoggerAppenderRollingFile extends LoggerAppenderFile { |
|
46 |
|
47 /** Compressing backup files is done in chunks, this determines how large. */ |
|
48 const COMPRESS_CHUNK_SIZE = 102400; // 100KB |
|
49 |
|
50 /** |
|
51 * The maximum size (in bytes) that the output file is allowed to reach |
|
52 * before being rolled over to backup files. |
|
53 * |
|
54 * The default maximum file size is 10MB (10485760 bytes). Maximum value |
|
55 * for this option may depend on the file system. |
|
56 * |
|
57 * @var integer |
|
58 */ |
|
59 protected $maxFileSize = 10485760; |
|
60 |
|
61 /** |
|
62 * Set the maximum number of backup files to keep around. |
|
63 * |
|
64 * Determines how many backup files are kept before the oldest is erased. |
|
65 * This option takes a positive integer value. If set to zero, then there |
|
66 * will be no backup files and the log file will be truncated when it |
|
67 * reaches <var>maxFileSize</var>. |
|
68 * |
|
69 * There is one backup file by default. |
|
70 * |
|
71 * @var integer |
|
72 */ |
|
73 protected $maxBackupIndex = 1; |
|
74 |
|
75 /** |
|
76 * The <var>compress</var> parameter determindes the compression with zlib. |
|
77 * If set to true, the rollover files are compressed and saved with the .gz extension. |
|
78 * @var boolean |
|
79 */ |
|
80 protected $compress = false; |
|
81 |
|
82 /** |
|
83 * Set to true in the constructor if PHP >= 5.3.0. In that case clearstatcache |
|
84 * supports conditional clearing of statistics. |
|
85 * @var boolean |
|
86 * @see http://php.net/manual/en/function.clearstatcache.php |
|
87 */ |
|
88 private $clearConditional = false; |
|
89 |
|
90 /** |
|
91 * Get the maximum size that the output file is allowed to reach |
|
92 * before being rolled over to backup files. |
|
93 * @return integer |
|
94 */ |
|
95 public function getMaximumFileSize() { |
|
96 return $this->maxFileSize; |
|
97 } |
|
98 |
|
99 public function __construct($name = '') { |
|
100 parent::__construct($name); |
|
101 if (version_compare(PHP_VERSION, '5.3.0') >= 0) { |
|
102 $this->clearConditional = true; |
|
103 } |
|
104 } |
|
105 |
|
106 /** |
|
107 * Implements the usual roll over behaviour. |
|
108 * |
|
109 * If MaxBackupIndex is positive, then files File.1, ..., File.MaxBackupIndex -1 are renamed to File.2, ..., File.MaxBackupIndex. |
|
110 * Moreover, File is renamed File.1 and closed. A new File is created to receive further log output. |
|
111 * |
|
112 * If MaxBackupIndex is equal to zero, then the File is truncated with no backup files created. |
|
113 * |
|
114 * Rollover must be called while the file is locked so that it is safe for concurrent access. |
|
115 * |
|
116 * @throws LoggerException If any part of the rollover procedure fails. |
|
117 */ |
|
118 private function rollOver() { |
|
119 // If maxBackups <= 0, then there is no file renaming to be done. |
|
120 if ($this->maxBackupIndex > 0) { |
|
121 // Delete the oldest file, to keep Windows happy. |
|
122 $file = $this->file . '.' . $this->maxBackupIndex; |
|
123 |
|
124 if (file_exists($file) && !unlink($file)) { |
|
125 throw new LoggerException("Unable to delete oldest backup file from [$file]."); |
|
126 } |
|
127 |
|
128 // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2} |
|
129 $this->renameArchievedLogs($this->file); |
|
130 |
|
131 // Backup the active file |
|
132 $this->moveToBackup($this->file); |
|
133 } |
|
134 |
|
135 // Truncate the active file |
|
136 ftruncate($this->fp, 0); |
|
137 rewind($this->fp); |
|
138 } |
|
139 |
|
140 private function moveToBackup($source) { |
|
141 if ($this->compress) { |
|
142 $target = $source . '.1.gz'; |
|
143 $this->compressFile($source, $target); |
|
144 } else { |
|
145 $target = $source . '.1'; |
|
146 copy($source, $target); |
|
147 } |
|
148 } |
|
149 |
|
150 private function compressFile($source, $target) { |
|
151 $target = 'compress.zlib://' . $target; |
|
152 |
|
153 $fin = fopen($source, 'rb'); |
|
154 if ($fin === false) { |
|
155 throw new LoggerException("Unable to open file for reading: [$source]."); |
|
156 } |
|
157 |
|
158 $fout = fopen($target, 'wb'); |
|
159 if ($fout === false) { |
|
160 throw new LoggerException("Unable to open file for writing: [$target]."); |
|
161 } |
|
162 |
|
163 while (!feof($fin)) { |
|
164 $chunk = fread($fin, self::COMPRESS_CHUNK_SIZE); |
|
165 if (false === fwrite($fout, $chunk)) { |
|
166 throw new LoggerException("Failed writing to compressed file."); |
|
167 } |
|
168 } |
|
169 |
|
170 fclose($fin); |
|
171 fclose($fout); |
|
172 } |
|
173 |
|
174 private function renameArchievedLogs($fileName) { |
|
175 for ($i = $this->maxBackupIndex - 1; $i >= 1; $i--) { |
|
176 |
|
177 $source = $fileName . "." . $i; |
|
178 if ($this->compress) { |
|
179 $source .= '.gz'; |
|
180 } |
|
181 |
|
182 if (file_exists($source)) { |
|
183 $target = $fileName . '.' . ($i + 1); |
|
184 if ($this->compress) { |
|
185 $target .= '.gz'; |
|
186 } |
|
187 |
|
188 rename($source, $target); |
|
189 } |
|
190 } |
|
191 } |
|
192 |
|
193 /** |
|
194 * Writes a string to the target file. Opens file if not already open. |
|
195 * @param string $string Data to write. |
|
196 */ |
|
197 protected function write($string) { |
|
198 // Lazy file open |
|
199 if (!isset($this->fp)) { |
|
200 if ($this->openFile() === false) { |
|
201 return; // Do not write if file open failed. |
|
202 } |
|
203 } |
|
204 |
|
205 // Lock the file while writing and possible rolling over |
|
206 if (flock($this->fp, LOCK_EX)) { |
|
207 |
|
208 // Write to locked file |
|
209 if (fwrite($this->fp, $string) === false) { |
|
210 $this->warn("Failed writing to file. Closing appender."); |
|
211 $this->closed = true; |
|
212 } |
|
213 |
|
214 // Stats cache must be cleared, otherwise filesize() returns cached results |
|
215 // If supported (PHP 5.3+), clear only the state cache for the target file |
|
216 if ($this->clearConditional) { |
|
217 clearstatcache(true, $this->file); |
|
218 } else { |
|
219 clearstatcache(); |
|
220 } |
|
221 |
|
222 // Rollover if needed |
|
223 if (filesize($this->file) > $this->maxFileSize) { |
|
224 try { |
|
225 $this->rollOver(); |
|
226 } catch (LoggerException $ex) { |
|
227 $this->warn("Rollover failed: " . $ex->getMessage() . " Closing appender."); |
|
228 $this->closed = true; |
|
229 } |
|
230 } |
|
231 |
|
232 flock($this->fp, LOCK_UN); |
|
233 } else { |
|
234 $this->warn("Failed locking file for writing. Closing appender."); |
|
235 $this->closed = true; |
|
236 } |
|
237 } |
|
238 |
|
239 public function activateOptions() { |
|
240 parent::activateOptions(); |
|
241 |
|
242 if ($this->compress && !extension_loaded('zlib')) { |
|
243 $this->warn("The 'zlib' extension is required for file compression. Disabling compression."); |
|
244 $this->compression = false; |
|
245 } |
|
246 } |
|
247 |
|
248 /** |
|
249 * Set the 'maxBackupIndex' parameter. |
|
250 * @param integer $maxBackupIndex |
|
251 */ |
|
252 public function setMaxBackupIndex($maxBackupIndex) { |
|
253 $this->setPositiveInteger('maxBackupIndex', $maxBackupIndex); |
|
254 } |
|
255 |
|
256 /** |
|
257 * Returns the 'maxBackupIndex' parameter. |
|
258 * @return integer |
|
259 */ |
|
260 public function getMaxBackupIndex() { |
|
261 return $this->maxBackupIndex; |
|
262 } |
|
263 |
|
264 /** |
|
265 * Set the 'maxFileSize' parameter. |
|
266 * @param mixed $maxFileSize |
|
267 */ |
|
268 public function setMaxFileSize($maxFileSize) { |
|
269 $this->setFileSize('maxFileSize', $maxFileSize); |
|
270 } |
|
271 |
|
272 /** |
|
273 * Returns the 'maxFileSize' parameter. |
|
274 * @return integer |
|
275 */ |
|
276 public function getMaxFileSize() { |
|
277 return $this->maxFileSize; |
|
278 } |
|
279 |
|
280 /** |
|
281 * Set the 'maxFileSize' parameter (kept for backward compatibility). |
|
282 * @param mixed $maxFileSize |
|
283 * @deprecated Use setMaxFileSize() instead. |
|
284 */ |
|
285 public function setMaximumFileSize($maxFileSize) { |
|
286 $this->warn("The 'maximumFileSize' parameter is deprecated. Use 'maxFileSize' instead."); |
|
287 return $this->setMaxFileSize($maxFileSize); |
|
288 } |
|
289 |
|
290 /** |
|
291 * Sets the 'compress' parameter. |
|
292 * @param boolean $compress |
|
293 */ |
|
294 public function setCompress($compress) { |
|
295 $this->setBoolean('compress', $compress); |
|
296 } |
|
297 |
|
298 /** |
|
299 * Returns the 'compress' parameter. |
|
300 * @param boolean |
|
301 */ |
|
302 public function getCompress() { |
|
303 return $this->compress; |
|
304 } |
|
305 } |
|