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
FileChannel()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
FileChannel(const std::string & path)55 FileChannel::FileChannel(const std::string& path):
56 _path(path),
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
~FileChannel()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
open()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
close()108 void FileChannel::close()
109 {
110 FastMutex::ScopedLock lock(_mutex);
111
112 delete _pFile;
113 _pFile = 0;
114 }
115
116
log(const Message & msg)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
setProperty(const std::string & name,const std::string & value)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
getProperty(const std::string & name) const178 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
creationDate() const203 Timestamp FileChannel::creationDate() const
204 {
205 if (_pFile)
206 return _pFile->creationDate();
207 else
208 return 0;
209 }
210
211
size() const212 UInt64 FileChannel::size() const
213 {
214 if (_pFile)
215 return _pFile->size();
216 else
217 return 0;
218 }
219
220
path() const221 const std::string& FileChannel::path() const
222 {
223 return _path;
224 }
225
226
setRotation(const std::string & rotation)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
setArchive(const std::string & archive)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
setCompress(const std::string & compress)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
setPurgeAge(const std::string & age)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
setPurgeCount(const std::string & count)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
setFlush(const std::string & flush)334 void FileChannel::setFlush(const std::string& flush)
335 {
336 _flush = icompare(flush, "true") == 0;
337 }
338
339
setRotateOnOpen(const std::string & rotateOnOpen)340 void FileChannel::setRotateOnOpen(const std::string& rotateOnOpen)
341 {
342 _rotateOnOpen = icompare(rotateOnOpen, "true") == 0;
343 }
344
345
purge()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
setNoPurge(const std::string & value)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
extractDigit(const std::string & value,std::string::const_iterator * nextToDigit) const374 int FileChannel::extractDigit(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
setPurgeStrategy(PurgeStrategy * strategy)395 void FileChannel::setPurgeStrategy(PurgeStrategy* strategy)
396 {
397 delete _pPurgeStrategy;
398 _pPurgeStrategy = strategy;
399 }
400
401
extractFactor(const std::string & value,std::string::const_iterator start) const402 Timespan::TimeDiff FileChannel::extractFactor(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