mirror of
https://github.com/pocoproject/poco.git
synced 2024-12-13 10:32:57 +01:00
Custom rotate, archive and purge strategies for FileChannel (#3810)
* Adding the ability to set custom rotate, archive and purge strategies. * Force CI
This commit is contained in:
parent
e0e628ac7e
commit
66e93f98cc
@ -42,6 +42,9 @@ public:
|
||||
ArchiveStrategy();
|
||||
virtual ~ArchiveStrategy();
|
||||
|
||||
virtual LogFile* open(LogFile* pFile) = 0;
|
||||
/// Open a new log file and return it.
|
||||
|
||||
virtual LogFile* archive(LogFile* pFile) = 0;
|
||||
/// Renames the given log file for archiving
|
||||
/// and creates and returns a new log file.
|
||||
@ -71,6 +74,8 @@ class Foundation_API ArchiveByNumberStrategy: public ArchiveStrategy
|
||||
public:
|
||||
ArchiveByNumberStrategy();
|
||||
~ArchiveByNumberStrategy();
|
||||
|
||||
LogFile* open(LogFile* pFile);
|
||||
LogFile* archive(LogFile* pFile);
|
||||
};
|
||||
|
||||
@ -89,6 +94,11 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
LogFile* open(LogFile* pFile)
|
||||
{
|
||||
return pFile;
|
||||
}
|
||||
|
||||
LogFile* archive(LogFile* pFile)
|
||||
/// Archives the file by appending the current timestamp to the
|
||||
/// file name. If the new file name exists, additionally a monotonic
|
||||
|
@ -211,6 +211,18 @@ public:
|
||||
/// See setProperty() for a description of the supported
|
||||
/// properties.
|
||||
|
||||
void setRotationStrategy(RotateStrategy* strategy);
|
||||
/// Set a rotation strategy.
|
||||
/// FileChannel will take ownership of the pointer
|
||||
|
||||
void setArchiveStrategy(ArchiveStrategy* strategy);
|
||||
/// Set an archive strategy.
|
||||
/// FileChannel will take ownership of the pointer
|
||||
|
||||
void setPurgeStrategy(PurgeStrategy* strategy);
|
||||
/// Set a purge strategy.
|
||||
/// FileChannel will take ownership of the pointer
|
||||
|
||||
Timestamp creationDate() const;
|
||||
/// Returns the log file's creation date.
|
||||
|
||||
@ -232,6 +244,7 @@ public:
|
||||
|
||||
protected:
|
||||
~FileChannel();
|
||||
|
||||
void setRotation(const std::string& rotation);
|
||||
void setArchive(const std::string& archive);
|
||||
void setCompress(const std::string& compress);
|
||||
@ -244,9 +257,11 @@ protected:
|
||||
private:
|
||||
bool setNoPurge(const std::string& value);
|
||||
int extractDigit(const std::string& value, std::string::const_iterator* nextToDigit = NULL) const;
|
||||
void setPurgeStrategy(PurgeStrategy* strategy);
|
||||
Timespan::TimeDiff extractFactor(const std::string& value, std::string::const_iterator start) const;
|
||||
|
||||
RotateStrategy* createRotationStrategy(const std::string& rotation, const std::string& times) const;
|
||||
ArchiveStrategy* createArchiveStrategy(const std::string& archive, const std::string& times) const;
|
||||
|
||||
std::string _path;
|
||||
std::string _times;
|
||||
std::string _rotation;
|
||||
|
@ -60,6 +60,16 @@ private:
|
||||
};
|
||||
|
||||
|
||||
class Foundation_API NullPurgeStrategy : public PurgeStrategy
|
||||
{
|
||||
public:
|
||||
NullPurgeStrategy();
|
||||
~NullPurgeStrategy();
|
||||
|
||||
void purge(const std::string& path);
|
||||
};
|
||||
|
||||
|
||||
class Foundation_API PurgeByAgeStrategy: public PurgeStrategy
|
||||
/// This purge strategy purges all files that have
|
||||
/// exceeded a given age (given in seconds).
|
||||
|
@ -49,6 +49,13 @@ private:
|
||||
};
|
||||
|
||||
|
||||
class Foundation_API NullRotateStrategy : public RotateStrategy
|
||||
{
|
||||
public:
|
||||
bool mustRotate(LogFile* pFile);
|
||||
};
|
||||
|
||||
|
||||
template <class DT>
|
||||
class RotateAtTimeStrategy: public RotateStrategy
|
||||
/// The file is rotated at specified [day,][hour]:minute
|
||||
|
@ -161,6 +161,12 @@ ArchiveByNumberStrategy::~ArchiveByNumberStrategy()
|
||||
}
|
||||
|
||||
|
||||
LogFile* ArchiveByNumberStrategy::open(LogFile* pFile)
|
||||
{
|
||||
return pFile;
|
||||
}
|
||||
|
||||
|
||||
LogFile* ArchiveByNumberStrategy::archive(LogFile* pFile)
|
||||
{
|
||||
std::string basePath = pFile->path();
|
||||
|
@ -45,9 +45,9 @@ FileChannel::FileChannel():
|
||||
_flush(true),
|
||||
_rotateOnOpen(false),
|
||||
_pFile(0),
|
||||
_pRotateStrategy(0),
|
||||
_pRotateStrategy(new NullRotateStrategy()),
|
||||
_pArchiveStrategy(new ArchiveByNumberStrategy),
|
||||
_pPurgeStrategy(0)
|
||||
_pPurgeStrategy(new NullPurgeStrategy())
|
||||
{
|
||||
}
|
||||
|
||||
@ -59,9 +59,9 @@ FileChannel::FileChannel(const std::string& path):
|
||||
_flush(true),
|
||||
_rotateOnOpen(false),
|
||||
_pFile(0),
|
||||
_pRotateStrategy(0),
|
||||
_pRotateStrategy(new NullRotateStrategy()),
|
||||
_pArchiveStrategy(new ArchiveByNumberStrategy),
|
||||
_pPurgeStrategy(0)
|
||||
_pPurgeStrategy(new NullPurgeStrategy())
|
||||
{
|
||||
}
|
||||
|
||||
@ -101,6 +101,8 @@ void FileChannel::open()
|
||||
_pFile = new LogFile(_path);
|
||||
}
|
||||
}
|
||||
|
||||
_pFile = _pArchiveStrategy->open(_pFile);
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,7 +122,7 @@ void FileChannel::log(const Message& msg)
|
||||
|
||||
FastMutex::ScopedLock lock(_mutex);
|
||||
|
||||
if (_pRotateStrategy && _pArchiveStrategy && _pRotateStrategy->mustRotate(_pFile))
|
||||
if (_pRotateStrategy->mustRotate(_pFile))
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -224,7 +226,7 @@ const std::string& FileChannel::path() const
|
||||
}
|
||||
|
||||
|
||||
void FileChannel::setRotation(const std::string& rotation)
|
||||
RotateStrategy* FileChannel::createRotationStrategy(const std::string& rotation, const std::string& times) const
|
||||
{
|
||||
std::string::const_iterator it = rotation.begin();
|
||||
std::string::const_iterator end = rotation.end();
|
||||
@ -238,12 +240,12 @@ void FileChannel::setRotation(const std::string& rotation)
|
||||
RotateStrategy* pStrategy = 0;
|
||||
if ((rotation.find(',') != std::string::npos) || (rotation.find(':') != std::string::npos))
|
||||
{
|
||||
if (_times == "utc")
|
||||
if (times == "utc")
|
||||
pStrategy = new RotateAtTimeStrategy<DateTime>(rotation);
|
||||
else if (_times == "local")
|
||||
else if (times == "local")
|
||||
pStrategy = new RotateAtTimeStrategy<LocalDateTime>(rotation);
|
||||
else
|
||||
throw PropertyNotSupportedException("times", _times);
|
||||
throw PropertyNotSupportedException("times", times);
|
||||
}
|
||||
else if (unit == "daily")
|
||||
pStrategy = new RotateByIntervalStrategy(Timespan(1*Timespan::DAYS));
|
||||
@ -271,12 +273,57 @@ void FileChannel::setRotation(const std::string& rotation)
|
||||
pStrategy = new RotateBySizeStrategy(n);
|
||||
else if (unit != "never")
|
||||
throw InvalidArgumentException("rotation", rotation);
|
||||
|
||||
return pStrategy;
|
||||
}
|
||||
|
||||
|
||||
void FileChannel::setRotationStrategy(RotateStrategy* strategy)
|
||||
{
|
||||
poco_check_ptr(strategy);
|
||||
|
||||
delete _pRotateStrategy;
|
||||
_pRotateStrategy = pStrategy;
|
||||
_pRotateStrategy = strategy;
|
||||
}
|
||||
|
||||
|
||||
void FileChannel::setRotation(const std::string& rotation)
|
||||
{
|
||||
setRotationStrategy(createRotationStrategy(rotation, _times));
|
||||
_rotation = rotation;
|
||||
}
|
||||
|
||||
|
||||
ArchiveStrategy* FileChannel::createArchiveStrategy(const std::string& archive, const std::string& times) const
|
||||
{
|
||||
ArchiveStrategy* pStrategy = 0;
|
||||
if (archive == "number")
|
||||
{
|
||||
pStrategy = new ArchiveByNumberStrategy;
|
||||
}
|
||||
else if (archive == "timestamp")
|
||||
{
|
||||
if (times == "utc")
|
||||
pStrategy = new ArchiveByTimestampStrategy<DateTime>;
|
||||
else if (times == "local")
|
||||
pStrategy = new ArchiveByTimestampStrategy<LocalDateTime>;
|
||||
else
|
||||
throw PropertyNotSupportedException("times", times);
|
||||
}
|
||||
else throw InvalidArgumentException("archive", archive);
|
||||
return pStrategy;
|
||||
}
|
||||
|
||||
|
||||
void FileChannel::setArchiveStrategy(ArchiveStrategy* strategy)
|
||||
{
|
||||
poco_check_ptr(strategy);
|
||||
|
||||
delete _pArchiveStrategy;
|
||||
_pArchiveStrategy = strategy;
|
||||
}
|
||||
|
||||
|
||||
void FileChannel::setArchive(const std::string& archive)
|
||||
{
|
||||
ArchiveStrategy* pStrategy = 0;
|
||||
@ -304,8 +351,7 @@ void FileChannel::setArchive(const std::string& archive)
|
||||
void FileChannel::setCompress(const std::string& compress)
|
||||
{
|
||||
_compress = icompare(compress, "true") == 0;
|
||||
if (_pArchiveStrategy)
|
||||
_pArchiveStrategy->compress(_compress);
|
||||
_pArchiveStrategy->compress(_compress);
|
||||
}
|
||||
|
||||
|
||||
@ -345,15 +391,12 @@ void FileChannel::setRotateOnOpen(const std::string& rotateOnOpen)
|
||||
|
||||
void FileChannel::purge()
|
||||
{
|
||||
if (_pPurgeStrategy)
|
||||
try
|
||||
{
|
||||
_pPurgeStrategy->purge(_path);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
try
|
||||
{
|
||||
_pPurgeStrategy->purge(_path);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -363,7 +406,7 @@ bool FileChannel::setNoPurge(const std::string& value)
|
||||
if (value.empty() || 0 == icompare(value, "none"))
|
||||
{
|
||||
delete _pPurgeStrategy;
|
||||
_pPurgeStrategy = 0;
|
||||
_pPurgeStrategy = new NullPurgeStrategy();
|
||||
_purgeAge = "none";
|
||||
return true;
|
||||
}
|
||||
@ -394,6 +437,8 @@ int FileChannel::extractDigit(const std::string& value, std::string::const_itera
|
||||
|
||||
void FileChannel::setPurgeStrategy(PurgeStrategy* strategy)
|
||||
{
|
||||
poco_check_ptr(strategy);
|
||||
|
||||
delete _pPurgeStrategy;
|
||||
_pPurgeStrategy = strategy;
|
||||
}
|
||||
|
@ -57,6 +57,26 @@ void PurgeStrategy::list(const std::string& path, std::vector<File>& files)
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// NullPurgeStrategy
|
||||
//
|
||||
|
||||
|
||||
NullPurgeStrategy::NullPurgeStrategy()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
NullPurgeStrategy::~NullPurgeStrategy()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void NullPurgeStrategy::purge(const std::string& path)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// PurgeByAgeStrategy
|
||||
//
|
||||
|
@ -38,6 +38,17 @@ RotateStrategy::~RotateStrategy()
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// NullRotateStrategy
|
||||
//
|
||||
|
||||
|
||||
bool NullRotateStrategy::mustRotate(LogFile* pFile)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// RotateByIntervalStrategy
|
||||
//
|
||||
|
@ -26,6 +26,9 @@
|
||||
#include "Poco/NumberFormatter.h"
|
||||
#include "Poco/DirectoryIterator.h"
|
||||
#include "Poco/Exception.h"
|
||||
#include "Poco/RotateStrategy.h"
|
||||
#include "Poco/ArchiveStrategy.h"
|
||||
#include "Poco/PurgeStrategy.h"
|
||||
#include <vector>
|
||||
|
||||
|
||||
@ -287,6 +290,57 @@ void FileChannelTest::testRotateAtTimeMinLocal()
|
||||
}
|
||||
|
||||
|
||||
class RotateByCustomStrategy : public Poco::RotateStrategy
|
||||
/// The file is rotated when the log file
|
||||
/// exceeds a given age.
|
||||
///
|
||||
/// For this to work reliably across all platforms and file systems
|
||||
/// (there are severe issues on most platforms finding out the real
|
||||
/// creation date of a file), the creation date of the file is
|
||||
/// written into the log file as the first entry.
|
||||
{
|
||||
public:
|
||||
bool mustRotate(Poco::LogFile* pFile)
|
||||
{
|
||||
return pFile->size() > 2000;
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
|
||||
void FileChannelTest::testRotateByStrategy()
|
||||
{
|
||||
std::string name = filename();
|
||||
try
|
||||
{
|
||||
AutoPtr<FileChannel> pChannel = new FileChannel(name);
|
||||
|
||||
// this test rotates at 2k just like testRotateBySize. Set the prop rotation to 50k to verify that the rotation strategy takes over
|
||||
pChannel->setProperty(FileChannel::PROP_ROTATION, "50 K");
|
||||
pChannel->setRotationStrategy(new RotateByCustomStrategy());
|
||||
pChannel->open();
|
||||
Message msg("source", "This is a log file entry", Message::PRIO_INFORMATION);
|
||||
for (int i = 0; i < 200; ++i)
|
||||
{
|
||||
pChannel->log(msg);
|
||||
}
|
||||
File f(name + ".0");
|
||||
assertTrue(f.exists());
|
||||
f = name + ".1";
|
||||
assertTrue(f.exists());
|
||||
f = name + ".2";
|
||||
assertTrue(!f.exists());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
remove(name);
|
||||
throw;
|
||||
}
|
||||
remove(name);
|
||||
}
|
||||
|
||||
|
||||
void FileChannelTest::testArchive()
|
||||
{
|
||||
std::string name = filename();
|
||||
@ -313,6 +367,88 @@ void FileChannelTest::testArchive()
|
||||
}
|
||||
|
||||
|
||||
class ArchiveByCustomNumberStrategy : public Poco::ArchiveStrategy
|
||||
/// A monotonic increasing number is appended to the
|
||||
/// log file name. The most recent archived file
|
||||
/// always has the number zero.
|
||||
{
|
||||
public:
|
||||
Poco::LogFile* open(Poco::LogFile* pFile)
|
||||
{
|
||||
return pFile;
|
||||
}
|
||||
|
||||
|
||||
Poco::LogFile* archive(Poco::LogFile* pFile)
|
||||
{
|
||||
std::string basePath = pFile->path();
|
||||
delete pFile;
|
||||
int n = -1;
|
||||
std::string path;
|
||||
do
|
||||
{
|
||||
path = basePath;
|
||||
path = path.substr(0, path.length() - 4);
|
||||
path.append("_");
|
||||
NumberFormatter::append(path, ++n);
|
||||
path.append(".log");
|
||||
} while (exists(path));
|
||||
|
||||
while (n >= 0)
|
||||
{
|
||||
std::string oldPath = basePath;
|
||||
if (n > 0)
|
||||
{
|
||||
oldPath = oldPath.substr(0, oldPath.length() - 4);
|
||||
oldPath.append("_");
|
||||
NumberFormatter::append(oldPath, n - 1);
|
||||
oldPath.append(".log");
|
||||
}
|
||||
std::string newPath = basePath;
|
||||
newPath = newPath.substr(0, newPath.length() - 4);
|
||||
newPath.append("_");
|
||||
NumberFormatter::append(newPath, n);
|
||||
newPath.append(".log");
|
||||
moveFile(oldPath, newPath);
|
||||
--n;
|
||||
}
|
||||
return new Poco::LogFile(basePath);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void FileChannelTest::testArchiveByStrategy()
|
||||
{
|
||||
std::string name = filename();
|
||||
try
|
||||
{
|
||||
AutoPtr<FileChannel> pChannel = new FileChannel(name);
|
||||
pChannel->setProperty(FileChannel::PROP_ROTATION, "2 K");
|
||||
pChannel->setProperty(FileChannel::PROP_ARCHIVE, "number");
|
||||
|
||||
pChannel->setArchiveStrategy(new ArchiveByCustomNumberStrategy());
|
||||
|
||||
pChannel->open();
|
||||
Message msg("source", "This is a log file entry", Message::PRIO_INFORMATION);
|
||||
for (int i = 0; i < 200; ++i)
|
||||
{
|
||||
pChannel->log(msg);
|
||||
}
|
||||
name = name.substr(0, name.length() - 4);
|
||||
name.append("_0.log");
|
||||
|
||||
File f(name);
|
||||
assertTrue(f.exists());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
remove(name);
|
||||
throw;
|
||||
}
|
||||
remove(name);
|
||||
}
|
||||
|
||||
|
||||
void FileChannelTest::testCompress()
|
||||
{
|
||||
std::string name = filename();
|
||||
@ -544,6 +680,40 @@ void FileChannelTest::testWrongPurgeOption()
|
||||
}
|
||||
|
||||
|
||||
void FileChannelTest::testPurgeByStrategy()
|
||||
{
|
||||
std::string name = filename();
|
||||
try
|
||||
{
|
||||
AutoPtr<FileChannel> pChannel = new FileChannel(name);
|
||||
pChannel->setProperty(FileChannel::PROP_ROTATION, "1 K");
|
||||
pChannel->setProperty(FileChannel::PROP_ARCHIVE, "number");
|
||||
pChannel->setProperty(FileChannel::PROP_PURGECOUNT, "");
|
||||
// simpler to test the type that already exists. A true "custom" purge strategy might be time based or total size based
|
||||
pChannel->setPurgeStrategy(new Poco::PurgeByCountStrategy(2));
|
||||
pChannel->open();
|
||||
Message msg("source", "This is a log file entry", Message::PRIO_INFORMATION);
|
||||
for (int i = 0; i < 200; ++i)
|
||||
{
|
||||
pChannel->log(msg);
|
||||
Thread::sleep(50);
|
||||
}
|
||||
File f0(name + ".0");
|
||||
assertTrue(f0.exists());
|
||||
File f1(name + ".1");
|
||||
assertTrue(f1.exists());
|
||||
File f2(name + ".2");
|
||||
assertTrue(!f2.exists());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
remove(name);
|
||||
throw;
|
||||
}
|
||||
remove(name);
|
||||
}
|
||||
|
||||
|
||||
void FileChannelTest::setUp()
|
||||
{
|
||||
}
|
||||
@ -642,11 +812,14 @@ CppUnit::Test* FileChannelTest::suite()
|
||||
CppUnit_addLongTest(pSuite, FileChannelTest, testRotateAtTimeHourLocal);
|
||||
CppUnit_addLongTest(pSuite, FileChannelTest, testRotateAtTimeMinUTC);
|
||||
CppUnit_addLongTest(pSuite, FileChannelTest, testRotateAtTimeMinLocal);
|
||||
CppUnit_addTest(pSuite, FileChannelTest, testRotateByStrategy);
|
||||
CppUnit_addTest(pSuite, FileChannelTest, testArchive);
|
||||
CppUnit_addTest(pSuite, FileChannelTest, testArchiveByStrategy);
|
||||
CppUnit_addTest(pSuite, FileChannelTest, testCompress);
|
||||
CppUnit_addLongTest(pSuite, FileChannelTest, testPurgeAge);
|
||||
CppUnit_addTest(pSuite, FileChannelTest, testPurgeCount);
|
||||
CppUnit_addTest(pSuite, FileChannelTest, testWrongPurgeOption);
|
||||
CppUnit_addTest(pSuite, FileChannelTest, testPurgeByStrategy);
|
||||
|
||||
return pSuite;
|
||||
}
|
||||
|
@ -39,11 +39,14 @@ public:
|
||||
void testRotateAtTimeHourLocal();
|
||||
void testRotateAtTimeMinUTC();
|
||||
void testRotateAtTimeMinLocal();
|
||||
void testRotateByStrategy();
|
||||
void testArchive();
|
||||
void testArchiveByStrategy();
|
||||
void testCompress();
|
||||
void testPurgeAge();
|
||||
void testPurgeCount();
|
||||
void testWrongPurgeOption();
|
||||
void testPurgeByStrategy();
|
||||
|
||||
void setUp();
|
||||
void tearDown();
|
||||
|
Loading…
Reference in New Issue
Block a user