1 | // |
2 | // FileChannel.cpp |
3 | // |
4 | // Library: Foundation |
5 | // Package: Logging |
6 | // Module: FileChannel |
7 | // |
8 | // Copyright (c) 2004-2006, Applied Informatics Software Engineering GmbH. |
9 | // and Contributors. |
10 | // |
11 | // SPDX-License-Identifier: BSL-1.0 |
12 | // |
13 | |
14 | |
15 | #include "Poco/FileChannel.h" |
16 | #include "Poco/ArchiveStrategy.h" |
17 | #include "Poco/RotateStrategy.h" |
18 | #include "Poco/PurgeStrategy.h" |
19 | #include "Poco/Message.h" |
20 | #include "Poco/NumberParser.h" |
21 | #include "Poco/DateTimeFormatter.h" |
22 | #include "Poco/DateTime.h" |
23 | #include "Poco/LocalDateTime.h" |
24 | #include "Poco/String.h" |
25 | #include "Poco/Exception.h" |
26 | #include "Poco/Ascii.h" |
27 | |
28 | |
29 | namespace Poco { |
30 | |
31 | |
32 | const std::string FileChannel::PROP_PATH = "path" ; |
33 | const std::string FileChannel::PROP_ROTATION = "rotation" ; |
34 | const std::string FileChannel::PROP_ARCHIVE = "archive" ; |
35 | const std::string FileChannel::PROP_TIMES = "times" ; |
36 | const std::string FileChannel::PROP_COMPRESS = "compress" ; |
37 | const std::string FileChannel::PROP_PURGEAGE = "purgeAge" ; |
38 | const std::string FileChannel::PROP_PURGECOUNT = "purgeCount" ; |
39 | const std::string FileChannel::PROP_FLUSH = "flush" ; |
40 | const std::string FileChannel::PROP_ROTATEONOPEN = "rotateOnOpen" ; |
41 | |
42 | FileChannel::FileChannel(): |
43 | _times("utc" ), |
44 | _compress(false), |
45 | _flush(true), |
46 | _rotateOnOpen(false), |
47 | _pFile(0), |
48 | _pRotateStrategy(0), |
49 | _pArchiveStrategy(new ArchiveByNumberStrategy), |
50 | _pPurgeStrategy(0) |
51 | { |
52 | } |
53 | |
54 | |
55 | FileChannel::FileChannel(const std::string& rPath): |
56 | _path(rPath), |
57 | _times("utc" ), |
58 | _compress(false), |
59 | _flush(true), |
60 | _rotateOnOpen(false), |
61 | _pFile(0), |
62 | _pRotateStrategy(0), |
63 | _pArchiveStrategy(new ArchiveByNumberStrategy), |
64 | _pPurgeStrategy(0) |
65 | { |
66 | } |
67 | |
68 | |
69 | FileChannel::~FileChannel() |
70 | { |
71 | try |
72 | { |
73 | close(); |
74 | delete _pRotateStrategy; |
75 | delete _pArchiveStrategy; |
76 | delete _pPurgeStrategy; |
77 | } |
78 | catch (...) |
79 | { |
80 | poco_unexpected(); |
81 | } |
82 | } |
83 | |
84 | |
85 | void FileChannel::open() |
86 | { |
87 | FastMutex::ScopedLock lock(_mutex); |
88 | |
89 | unsafeOpen(); |
90 | } |
91 | |
92 | |
93 | void FileChannel::close() |
94 | { |
95 | FastMutex::ScopedLock lock(_mutex); |
96 | |
97 | delete _pFile; |
98 | _pFile = 0; |
99 | } |
100 | |
101 | |
102 | void FileChannel::log(const Message& msg) |
103 | { |
104 | FastMutex::ScopedLock lock(_mutex); |
105 | |
106 | unsafeOpen(); |
107 | |
108 | if (_pRotateStrategy && _pArchiveStrategy && _pRotateStrategy->mustRotate(_pFile)) |
109 | { |
110 | try |
111 | { |
112 | _pFile = _pArchiveStrategy->archive(_pFile); |
113 | purge(); |
114 | } |
115 | catch (...) |
116 | { |
117 | _pFile = new LogFile(_path); |
118 | } |
119 | // we must call mustRotate() again to give the |
120 | // RotateByIntervalStrategy a chance to write its timestamp |
121 | // to the new file. |
122 | _pRotateStrategy->mustRotate(_pFile); |
123 | } |
124 | |
125 | try |
126 | { |
127 | _pFile->write(msg.getText(), _flush); |
128 | } |
129 | catch (const WriteFileException & e) |
130 | { |
131 | // In case of no space left on device, |
132 | // we try to purge old files or truncate current file. |
133 | |
134 | // NOTE: error reason is not preserved in WriteFileException, we need to check errno manually. |
135 | // NOTE: other reasons like quota exceeded are not handled. |
136 | // NOTE: current log message will be lost. |
137 | |
138 | if (errno == ENOSPC) |
139 | { |
140 | PurgeOneFileStrategy().purge(_path); |
141 | } |
142 | } |
143 | } |
144 | |
145 | |
146 | void FileChannel::setProperty(const std::string& name, const std::string& value) |
147 | { |
148 | FastMutex::ScopedLock lock(_mutex); |
149 | |
150 | if (name == PROP_TIMES) |
151 | { |
152 | _times = value; |
153 | |
154 | if (!_rotation.empty()) |
155 | setRotation(_rotation); |
156 | |
157 | if (!_archive.empty()) |
158 | setArchive(_archive); |
159 | } |
160 | else if (name == PROP_PATH) |
161 | _path = value; |
162 | else if (name == PROP_ROTATION) |
163 | setRotation(value); |
164 | else if (name == PROP_ARCHIVE) |
165 | setArchive(value); |
166 | else if (name == PROP_COMPRESS) |
167 | setCompress(value); |
168 | else if (name == PROP_PURGEAGE) |
169 | setPurgeAge(value); |
170 | else if (name == PROP_PURGECOUNT) |
171 | setPurgeCount(value); |
172 | else if (name == PROP_FLUSH) |
173 | setFlush(value); |
174 | else if (name == PROP_ROTATEONOPEN) |
175 | setRotateOnOpen(value); |
176 | else |
177 | Channel::setProperty(name, value); |
178 | } |
179 | |
180 | |
181 | std::string FileChannel::getProperty(const std::string& name) const |
182 | { |
183 | if (name == PROP_TIMES) |
184 | return _times; |
185 | else if (name == PROP_PATH) |
186 | return _path; |
187 | else if (name == PROP_ROTATION) |
188 | return _rotation; |
189 | else if (name == PROP_ARCHIVE) |
190 | return _archive; |
191 | else if (name == PROP_COMPRESS) |
192 | return std::string(_compress ? "true" : "false" ); |
193 | else if (name == PROP_PURGEAGE) |
194 | return _purgeAge; |
195 | else if (name == PROP_PURGECOUNT) |
196 | return _purgeCount; |
197 | else if (name == PROP_FLUSH) |
198 | return std::string(_flush ? "true" : "false" ); |
199 | else if (name == PROP_ROTATEONOPEN) |
200 | return std::string(_rotateOnOpen ? "true" : "false" ); |
201 | else |
202 | return Channel::getProperty(name); |
203 | } |
204 | |
205 | |
206 | Timestamp FileChannel::creationDate() const |
207 | { |
208 | if (_pFile) |
209 | return _pFile->creationDate(); |
210 | else |
211 | return 0; |
212 | } |
213 | |
214 | |
215 | UInt64 FileChannel::size() const |
216 | { |
217 | if (_pFile) |
218 | return _pFile->size(); |
219 | else |
220 | return 0; |
221 | } |
222 | |
223 | |
224 | const std::string& FileChannel::path() const |
225 | { |
226 | return _path; |
227 | } |
228 | |
229 | |
230 | void FileChannel::setRotation(const std::string& rotation) |
231 | { |
232 | std::string::const_iterator it = rotation.begin(); |
233 | std::string::const_iterator end = rotation.end(); |
234 | int n = 0; |
235 | while (it != end && Ascii::isSpace(*it)) ++it; |
236 | while (it != end && Ascii::isDigit(*it)) { n *= 10; n += *it++ - '0'; } |
237 | while (it != end && Ascii::isSpace(*it)) ++it; |
238 | std::string unit; |
239 | while (it != end && Ascii::isAlpha(*it)) unit += *it++; |
240 | |
241 | RotateStrategy* pStrategy = 0; |
242 | if ((rotation.find(',') != std::string::npos) || (rotation.find(':') != std::string::npos)) |
243 | { |
244 | if (_times == "utc" ) |
245 | pStrategy = new RotateAtTimeStrategy<DateTime>(rotation); |
246 | else if (_times == "local" ) |
247 | pStrategy = new RotateAtTimeStrategy<LocalDateTime>(rotation); |
248 | else |
249 | throw PropertyNotSupportedException("times" , _times); |
250 | } |
251 | else if (unit == "daily" ) |
252 | pStrategy = new RotateByIntervalStrategy(Timespan(1*Timespan::DAYS)); |
253 | else if (unit == "weekly" ) |
254 | pStrategy = new RotateByIntervalStrategy(Timespan(7*Timespan::DAYS)); |
255 | else if (unit == "monthly" ) |
256 | pStrategy = new RotateByIntervalStrategy(Timespan(30*Timespan::DAYS)); |
257 | else if (unit == "seconds" ) // for testing only |
258 | pStrategy = new RotateByIntervalStrategy(Timespan(n*Timespan::SECONDS)); |
259 | else if (unit == "minutes" ) |
260 | pStrategy = new RotateByIntervalStrategy(Timespan(n*Timespan::MINUTES)); |
261 | else if (unit == "hours" ) |
262 | pStrategy = new RotateByIntervalStrategy(Timespan(n*Timespan::HOURS)); |
263 | else if (unit == "days" ) |
264 | pStrategy = new RotateByIntervalStrategy(Timespan(n*Timespan::DAYS)); |
265 | else if (unit == "weeks" ) |
266 | pStrategy = new RotateByIntervalStrategy(Timespan(n*7*Timespan::DAYS)); |
267 | else if (unit == "months" ) |
268 | pStrategy = new RotateByIntervalStrategy(Timespan(n*30*Timespan::DAYS)); |
269 | else if (unit == "K" ) |
270 | pStrategy = new RotateBySizeStrategy(n*1024); |
271 | else if (unit == "M" ) |
272 | pStrategy = new RotateBySizeStrategy(n*1024*1024); |
273 | else if (unit.empty()) |
274 | pStrategy = new RotateBySizeStrategy(n); |
275 | else if (unit != "never" ) |
276 | throw InvalidArgumentException("rotation" , rotation); |
277 | delete _pRotateStrategy; |
278 | _pRotateStrategy = pStrategy; |
279 | _rotation = rotation; |
280 | } |
281 | |
282 | |
283 | void FileChannel::setArchive(const std::string& archive) |
284 | { |
285 | ArchiveStrategy* pStrategy = 0; |
286 | if (archive == "number" ) |
287 | { |
288 | pStrategy = new ArchiveByNumberStrategy; |
289 | } |
290 | else if (archive == "timestamp" ) |
291 | { |
292 | if (_times == "utc" ) |
293 | pStrategy = new ArchiveByTimestampStrategy<DateTime>; |
294 | else if (_times == "local" ) |
295 | pStrategy = new ArchiveByTimestampStrategy<LocalDateTime>; |
296 | else |
297 | throw PropertyNotSupportedException("times" , _times); |
298 | } |
299 | else throw InvalidArgumentException("archive" , archive); |
300 | delete _pArchiveStrategy; |
301 | pStrategy->compress(_compress); |
302 | _pArchiveStrategy = pStrategy; |
303 | _archive = archive; |
304 | } |
305 | |
306 | |
307 | void FileChannel::setCompress(const std::string& compress) |
308 | { |
309 | _compress = icompare(compress, "true" ) == 0; |
310 | if (_pArchiveStrategy) |
311 | _pArchiveStrategy->compress(_compress); |
312 | } |
313 | |
314 | |
315 | void FileChannel::setPurgeAge(const std::string& age) |
316 | { |
317 | if (setNoPurge(age)) return; |
318 | |
319 | std::string::const_iterator nextToDigit; |
320 | int num = extractDigit(age, &nextToDigit); |
321 | Timespan::TimeDiff factor = extractFactor(age, nextToDigit); |
322 | |
323 | setPurgeStrategy(new PurgeByAgeStrategy(Timespan(num * factor))); |
324 | _purgeAge = age; |
325 | } |
326 | |
327 | |
328 | void FileChannel::setPurgeCount(const std::string& count) |
329 | { |
330 | if (setNoPurge(count)) return; |
331 | |
332 | setPurgeStrategy(new PurgeByCountStrategy(extractDigit(count))); |
333 | _purgeCount = count; |
334 | } |
335 | |
336 | |
337 | void FileChannel::setFlush(const std::string& flush) |
338 | { |
339 | _flush = icompare(flush, "true" ) == 0; |
340 | } |
341 | |
342 | |
343 | void FileChannel::setRotateOnOpen(const std::string& rotateOnOpen) |
344 | { |
345 | _rotateOnOpen = icompare(rotateOnOpen, "true" ) == 0; |
346 | } |
347 | |
348 | |
349 | void FileChannel::purge() |
350 | { |
351 | if (_pPurgeStrategy) |
352 | { |
353 | try |
354 | { |
355 | _pPurgeStrategy->purge(_path); |
356 | } |
357 | catch (...) |
358 | { |
359 | } |
360 | } |
361 | } |
362 | |
363 | |
364 | void FileChannel::unsafeOpen() |
365 | { |
366 | if (!_pFile) |
367 | { |
368 | _pFile = new LogFile(_path); |
369 | if (_rotateOnOpen && _pFile->size() > 0) |
370 | { |
371 | try |
372 | { |
373 | _pFile = _pArchiveStrategy->archive(_pFile); |
374 | purge(); |
375 | } |
376 | catch (...) |
377 | { |
378 | _pFile = new LogFile(_path); |
379 | } |
380 | } |
381 | } |
382 | } |
383 | |
384 | |
385 | bool FileChannel::setNoPurge(const std::string& value) |
386 | { |
387 | if (value.empty() || 0 == icompare(value, "none" )) |
388 | { |
389 | delete _pPurgeStrategy; |
390 | _pPurgeStrategy = 0; |
391 | _purgeAge = "none" ; |
392 | return true; |
393 | } |
394 | else return false; |
395 | } |
396 | |
397 | |
398 | int FileChannel::(const std::string& value, std::string::const_iterator* nextToDigit) const |
399 | { |
400 | std::string::const_iterator it = value.begin(); |
401 | std::string::const_iterator end = value.end(); |
402 | int digit = 0; |
403 | |
404 | while (it != end && Ascii::isSpace(*it)) ++it; |
405 | while (it != end && Ascii::isDigit(*it)) |
406 | { |
407 | digit *= 10; |
408 | digit += *it++ - '0'; |
409 | } |
410 | |
411 | if (digit == 0) |
412 | throw InvalidArgumentException("Zero is not valid purge age." ); |
413 | |
414 | if (nextToDigit) *nextToDigit = it; |
415 | return digit; |
416 | } |
417 | |
418 | |
419 | void FileChannel::setPurgeStrategy(PurgeStrategy* strategy) |
420 | { |
421 | delete _pPurgeStrategy; |
422 | _pPurgeStrategy = strategy; |
423 | } |
424 | |
425 | |
426 | Timespan::TimeDiff FileChannel::(const std::string& value, std::string::const_iterator start) const |
427 | { |
428 | while (start != value.end() && Ascii::isSpace(*start)) ++start; |
429 | |
430 | std::string unit; |
431 | while (start != value.end() && Ascii::isAlpha(*start)) unit += *start++; |
432 | |
433 | if (unit == "seconds" ) |
434 | return Timespan::SECONDS; |
435 | if (unit == "minutes" ) |
436 | return Timespan::MINUTES; |
437 | else if (unit == "hours" ) |
438 | return Timespan::HOURS; |
439 | else if (unit == "days" ) |
440 | return Timespan::DAYS; |
441 | else if (unit == "weeks" ) |
442 | return 7 * Timespan::DAYS; |
443 | else if (unit == "months" ) |
444 | return 30 * Timespan::DAYS; |
445 | else throw InvalidArgumentException("purgeAge" , value); |
446 | |
447 | return Timespan::TimeDiff(); |
448 | } |
449 | |
450 | |
451 | |
452 | } // namespace Poco |
453 | |