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
29namespace Poco {
30
31
32const std::string FileChannel::PROP_PATH = "path";
33const std::string FileChannel::PROP_ROTATION = "rotation";
34const std::string FileChannel::PROP_ARCHIVE = "archive";
35const std::string FileChannel::PROP_TIMES = "times";
36const std::string FileChannel::PROP_COMPRESS = "compress";
37const std::string FileChannel::PROP_PURGEAGE = "purgeAge";
38const std::string FileChannel::PROP_PURGECOUNT = "purgeCount";
39const std::string FileChannel::PROP_FLUSH = "flush";
40const std::string FileChannel::PROP_ROTATEONOPEN = "rotateOnOpen";
41
42FileChannel::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
55FileChannel::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
69FileChannel::~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
85void FileChannel::open()
86{
87 FastMutex::ScopedLock lock(_mutex);
88
89 unsafeOpen();
90}
91
92
93void FileChannel::close()
94{
95 FastMutex::ScopedLock lock(_mutex);
96
97 delete _pFile;
98 _pFile = 0;
99}
100
101
102void 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
146void 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
181std::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
206Timestamp FileChannel::creationDate() const
207{
208 if (_pFile)
209 return _pFile->creationDate();
210 else
211 return 0;
212}
213
214
215UInt64 FileChannel::size() const
216{
217 if (_pFile)
218 return _pFile->size();
219 else
220 return 0;
221}
222
223
224const std::string& FileChannel::path() const
225{
226 return _path;
227}
228
229
230void 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
283void 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
307void FileChannel::setCompress(const std::string& compress)
308{
309 _compress = icompare(compress, "true") == 0;
310 if (_pArchiveStrategy)
311 _pArchiveStrategy->compress(_compress);
312}
313
314
315void 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
328void 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
337void FileChannel::setFlush(const std::string& flush)
338{
339 _flush = icompare(flush, "true") == 0;
340}
341
342
343void FileChannel::setRotateOnOpen(const std::string& rotateOnOpen)
344{
345 _rotateOnOpen = icompare(rotateOnOpen, "true") == 0;
346}
347
348
349void FileChannel::purge()
350{
351 if (_pPurgeStrategy)
352 {
353 try
354 {
355 _pPurgeStrategy->purge(_path);
356 }
357 catch (...)
358 {
359 }
360 }
361}
362
363
364void 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
385bool 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
398int FileChannel::extractDigit(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
419void FileChannel::setPurgeStrategy(PurgeStrategy* strategy)
420{
421 delete _pPurgeStrategy;
422 _pPurgeStrategy = strategy;
423}
424
425
426Timespan::TimeDiff FileChannel::extractFactor(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