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 | if (!_pFile) |
90 | { |
91 | _pFile = new LogFile(_path); |
92 | if (_rotateOnOpen && _pFile->size() > 0) |
93 | { |
94 | try |
95 | { |
96 | _pFile = _pArchiveStrategy->archive(_pFile); |
97 | purge(); |
98 | } |
99 | catch (...) |
100 | { |
101 | _pFile = new LogFile(_path); |
102 | } |
103 | } |
104 | } |
105 | } |
106 | |
107 | |
108 | void FileChannel::close() |
109 | { |
110 | FastMutex::ScopedLock lock(_mutex); |
111 | |
112 | delete _pFile; |
113 | _pFile = 0; |
114 | } |
115 | |
116 | |
117 | void FileChannel::log(const Message& msg) |
118 | { |
119 | open(); |
120 | |
121 | FastMutex::ScopedLock lock(_mutex); |
122 | |
123 | if (_pRotateStrategy && _pArchiveStrategy && _pRotateStrategy->mustRotate(_pFile)) |
124 | { |
125 | try |
126 | { |
127 | _pFile = _pArchiveStrategy->archive(_pFile); |
128 | purge(); |
129 | } |
130 | catch (...) |
131 | { |
132 | _pFile = new LogFile(_path); |
133 | } |
134 | // we must call mustRotate() again to give the |
135 | // RotateByIntervalStrategy a chance to write its timestamp |
136 | // to the new file. |
137 | _pRotateStrategy->mustRotate(_pFile); |
138 | } |
139 | _pFile->write(msg.getText(), _flush); |
140 | } |
141 | |
142 | |
143 | void FileChannel::setProperty(const std::string& name, const std::string& value) |
144 | { |
145 | FastMutex::ScopedLock lock(_mutex); |
146 | |
147 | if (name == PROP_TIMES) |
148 | { |
149 | _times = value; |
150 | |
151 | if (!_rotation.empty()) |
152 | setRotation(_rotation); |
153 | |
154 | if (!_archive.empty()) |
155 | setArchive(_archive); |
156 | } |
157 | else if (name == PROP_PATH) |
158 | _path = value; |
159 | else if (name == PROP_ROTATION) |
160 | setRotation(value); |
161 | else if (name == PROP_ARCHIVE) |
162 | setArchive(value); |
163 | else if (name == PROP_COMPRESS) |
164 | setCompress(value); |
165 | else if (name == PROP_PURGEAGE) |
166 | setPurgeAge(value); |
167 | else if (name == PROP_PURGECOUNT) |
168 | setPurgeCount(value); |
169 | else if (name == PROP_FLUSH) |
170 | setFlush(value); |
171 | else if (name == PROP_ROTATEONOPEN) |
172 | setRotateOnOpen(value); |
173 | else |
174 | Channel::setProperty(name, value); |
175 | } |
176 | |
177 | |
178 | std::string FileChannel::getProperty(const std::string& name) const |
179 | { |
180 | if (name == PROP_TIMES) |
181 | return _times; |
182 | else if (name == PROP_PATH) |
183 | return _path; |
184 | else if (name == PROP_ROTATION) |
185 | return _rotation; |
186 | else if (name == PROP_ARCHIVE) |
187 | return _archive; |
188 | else if (name == PROP_COMPRESS) |
189 | return std::string(_compress ? "true" : "false" ); |
190 | else if (name == PROP_PURGEAGE) |
191 | return _purgeAge; |
192 | else if (name == PROP_PURGECOUNT) |
193 | return _purgeCount; |
194 | else if (name == PROP_FLUSH) |
195 | return std::string(_flush ? "true" : "false" ); |
196 | else if (name == PROP_ROTATEONOPEN) |
197 | return std::string(_rotateOnOpen ? "true" : "false" ); |
198 | else |
199 | return Channel::getProperty(name); |
200 | } |
201 | |
202 | |
203 | Timestamp FileChannel::creationDate() const |
204 | { |
205 | if (_pFile) |
206 | return _pFile->creationDate(); |
207 | else |
208 | return 0; |
209 | } |
210 | |
211 | |
212 | UInt64 FileChannel::size() const |
213 | { |
214 | if (_pFile) |
215 | return _pFile->size(); |
216 | else |
217 | return 0; |
218 | } |
219 | |
220 | |
221 | const std::string& FileChannel::path() const |
222 | { |
223 | return _path; |
224 | } |
225 | |
226 | |
227 | void FileChannel::setRotation(const std::string& rotation) |
228 | { |
229 | std::string::const_iterator it = rotation.begin(); |
230 | std::string::const_iterator end = rotation.end(); |
231 | int n = 0; |
232 | while (it != end && Ascii::isSpace(*it)) ++it; |
233 | while (it != end && Ascii::isDigit(*it)) { n *= 10; n += *it++ - '0'; } |
234 | while (it != end && Ascii::isSpace(*it)) ++it; |
235 | std::string unit; |
236 | while (it != end && Ascii::isAlpha(*it)) unit += *it++; |
237 | |
238 | RotateStrategy* pStrategy = 0; |
239 | if ((rotation.find(',') != std::string::npos) || (rotation.find(':') != std::string::npos)) |
240 | { |
241 | if (_times == "utc" ) |
242 | pStrategy = new RotateAtTimeStrategy<DateTime>(rotation); |
243 | else if (_times == "local" ) |
244 | pStrategy = new RotateAtTimeStrategy<LocalDateTime>(rotation); |
245 | else |
246 | throw PropertyNotSupportedException("times" , _times); |
247 | } |
248 | else if (unit == "daily" ) |
249 | pStrategy = new RotateByIntervalStrategy(Timespan(1*Timespan::DAYS)); |
250 | else if (unit == "weekly" ) |
251 | pStrategy = new RotateByIntervalStrategy(Timespan(7*Timespan::DAYS)); |
252 | else if (unit == "monthly" ) |
253 | pStrategy = new RotateByIntervalStrategy(Timespan(30*Timespan::DAYS)); |
254 | else if (unit == "seconds" ) // for testing only |
255 | pStrategy = new RotateByIntervalStrategy(Timespan(n*Timespan::SECONDS)); |
256 | else if (unit == "minutes" ) |
257 | pStrategy = new RotateByIntervalStrategy(Timespan(n*Timespan::MINUTES)); |
258 | else if (unit == "hours" ) |
259 | pStrategy = new RotateByIntervalStrategy(Timespan(n*Timespan::HOURS)); |
260 | else if (unit == "days" ) |
261 | pStrategy = new RotateByIntervalStrategy(Timespan(n*Timespan::DAYS)); |
262 | else if (unit == "weeks" ) |
263 | pStrategy = new RotateByIntervalStrategy(Timespan(n*7*Timespan::DAYS)); |
264 | else if (unit == "months" ) |
265 | pStrategy = new RotateByIntervalStrategy(Timespan(n*30*Timespan::DAYS)); |
266 | else if (unit == "K" ) |
267 | pStrategy = new RotateBySizeStrategy(n*1024); |
268 | else if (unit == "M" ) |
269 | pStrategy = new RotateBySizeStrategy(n*1024*1024); |
270 | else if (unit.empty()) |
271 | pStrategy = new RotateBySizeStrategy(n); |
272 | else if (unit != "never" ) |
273 | throw InvalidArgumentException("rotation" , rotation); |
274 | delete _pRotateStrategy; |
275 | _pRotateStrategy = pStrategy; |
276 | _rotation = rotation; |
277 | } |
278 | |
279 | |
280 | void FileChannel::setArchive(const std::string& archive) |
281 | { |
282 | ArchiveStrategy* pStrategy = 0; |
283 | if (archive == "number" ) |
284 | { |
285 | pStrategy = new ArchiveByNumberStrategy; |
286 | } |
287 | else if (archive == "timestamp" ) |
288 | { |
289 | if (_times == "utc" ) |
290 | pStrategy = new ArchiveByTimestampStrategy<DateTime>; |
291 | else if (_times == "local" ) |
292 | pStrategy = new ArchiveByTimestampStrategy<LocalDateTime>; |
293 | else |
294 | throw PropertyNotSupportedException("times" , _times); |
295 | } |
296 | else throw InvalidArgumentException("archive" , archive); |
297 | delete _pArchiveStrategy; |
298 | pStrategy->compress(_compress); |
299 | _pArchiveStrategy = pStrategy; |
300 | _archive = archive; |
301 | } |
302 | |
303 | |
304 | void FileChannel::setCompress(const std::string& compress) |
305 | { |
306 | _compress = icompare(compress, "true" ) == 0; |
307 | if (_pArchiveStrategy) |
308 | _pArchiveStrategy->compress(_compress); |
309 | } |
310 | |
311 | |
312 | void FileChannel::setPurgeAge(const std::string& age) |
313 | { |
314 | if (setNoPurge(age)) return; |
315 | |
316 | std::string::const_iterator nextToDigit; |
317 | int num = extractDigit(age, &nextToDigit); |
318 | Timespan::TimeDiff factor = extractFactor(age, nextToDigit); |
319 | |
320 | setPurgeStrategy(new PurgeByAgeStrategy(Timespan(num * factor))); |
321 | _purgeAge = age; |
322 | } |
323 | |
324 | |
325 | void FileChannel::setPurgeCount(const std::string& count) |
326 | { |
327 | if (setNoPurge(count)) return; |
328 | |
329 | setPurgeStrategy(new PurgeByCountStrategy(extractDigit(count))); |
330 | _purgeCount = count; |
331 | } |
332 | |
333 | |
334 | void FileChannel::setFlush(const std::string& flush) |
335 | { |
336 | _flush = icompare(flush, "true" ) == 0; |
337 | } |
338 | |
339 | |
340 | void FileChannel::setRotateOnOpen(const std::string& rotateOnOpen) |
341 | { |
342 | _rotateOnOpen = icompare(rotateOnOpen, "true" ) == 0; |
343 | } |
344 | |
345 | |
346 | void FileChannel::purge() |
347 | { |
348 | if (_pPurgeStrategy) |
349 | { |
350 | try |
351 | { |
352 | _pPurgeStrategy->purge(_path); |
353 | } |
354 | catch (...) |
355 | { |
356 | } |
357 | } |
358 | } |
359 | |
360 | |
361 | bool FileChannel::setNoPurge(const std::string& value) |
362 | { |
363 | if (value.empty() || 0 == icompare(value, "none" )) |
364 | { |
365 | delete _pPurgeStrategy; |
366 | _pPurgeStrategy = 0; |
367 | _purgeAge = "none" ; |
368 | return true; |
369 | } |
370 | else return false; |
371 | } |
372 | |
373 | |
374 | int FileChannel::(const std::string& value, std::string::const_iterator* nextToDigit) const |
375 | { |
376 | std::string::const_iterator it = value.begin(); |
377 | std::string::const_iterator end = value.end(); |
378 | int digit = 0; |
379 | |
380 | while (it != end && Ascii::isSpace(*it)) ++it; |
381 | while (it != end && Ascii::isDigit(*it)) |
382 | { |
383 | digit *= 10; |
384 | digit += *it++ - '0'; |
385 | } |
386 | |
387 | if (digit == 0) |
388 | throw InvalidArgumentException("Zero is not valid purge age." ); |
389 | |
390 | if (nextToDigit) *nextToDigit = it; |
391 | return digit; |
392 | } |
393 | |
394 | |
395 | void FileChannel::setPurgeStrategy(PurgeStrategy* strategy) |
396 | { |
397 | delete _pPurgeStrategy; |
398 | _pPurgeStrategy = strategy; |
399 | } |
400 | |
401 | |
402 | Timespan::TimeDiff FileChannel::(const std::string& value, std::string::const_iterator start) const |
403 | { |
404 | while (start != value.end() && Ascii::isSpace(*start)) ++start; |
405 | |
406 | std::string unit; |
407 | while (start != value.end() && Ascii::isAlpha(*start)) unit += *start++; |
408 | |
409 | if (unit == "seconds" ) |
410 | return Timespan::SECONDS; |
411 | if (unit == "minutes" ) |
412 | return Timespan::MINUTES; |
413 | else if (unit == "hours" ) |
414 | return Timespan::HOURS; |
415 | else if (unit == "days" ) |
416 | return Timespan::DAYS; |
417 | else if (unit == "weeks" ) |
418 | return 7 * Timespan::DAYS; |
419 | else if (unit == "months" ) |
420 | return 30 * Timespan::DAYS; |
421 | else throw InvalidArgumentException("purgeAge" , value); |
422 | |
423 | return Timespan::TimeDiff(); |
424 | } |
425 | |
426 | |
427 | |
428 | } // namespace Poco |
429 | |