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