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:
Andrew Auclair
2023-03-18 02:28:25 -04:00
committed by GitHub
parent e0e628ac7e
commit 66e93f98cc
10 changed files with 322 additions and 22 deletions

View File

@@ -42,6 +42,9 @@ public:
ArchiveStrategy(); ArchiveStrategy();
virtual ~ArchiveStrategy(); virtual ~ArchiveStrategy();
virtual LogFile* open(LogFile* pFile) = 0;
/// Open a new log file and return it.
virtual LogFile* archive(LogFile* pFile) = 0; virtual LogFile* archive(LogFile* pFile) = 0;
/// Renames the given log file for archiving /// Renames the given log file for archiving
/// and creates and returns a new log file. /// and creates and returns a new log file.
@@ -71,6 +74,8 @@ class Foundation_API ArchiveByNumberStrategy: public ArchiveStrategy
public: public:
ArchiveByNumberStrategy(); ArchiveByNumberStrategy();
~ArchiveByNumberStrategy(); ~ArchiveByNumberStrategy();
LogFile* open(LogFile* pFile);
LogFile* archive(LogFile* pFile); LogFile* archive(LogFile* pFile);
}; };
@@ -89,6 +94,11 @@ public:
{ {
} }
LogFile* open(LogFile* pFile)
{
return pFile;
}
LogFile* archive(LogFile* pFile) LogFile* archive(LogFile* pFile)
/// Archives the file by appending the current timestamp to the /// Archives the file by appending the current timestamp to the
/// file name. If the new file name exists, additionally a monotonic /// file name. If the new file name exists, additionally a monotonic

View File

@@ -211,6 +211,18 @@ public:
/// See setProperty() for a description of the supported /// See setProperty() for a description of the supported
/// properties. /// 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; Timestamp creationDate() const;
/// Returns the log file's creation date. /// Returns the log file's creation date.
@@ -232,6 +244,7 @@ public:
protected: protected:
~FileChannel(); ~FileChannel();
void setRotation(const std::string& rotation); void setRotation(const std::string& rotation);
void setArchive(const std::string& archive); void setArchive(const std::string& archive);
void setCompress(const std::string& compress); void setCompress(const std::string& compress);
@@ -244,9 +257,11 @@ protected:
private: private:
bool setNoPurge(const std::string& value); bool setNoPurge(const std::string& value);
int extractDigit(const std::string& value, std::string::const_iterator* nextToDigit = NULL) const; 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; 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 _path;
std::string _times; std::string _times;
std::string _rotation; std::string _rotation;

View File

@@ -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 class Foundation_API PurgeByAgeStrategy: public PurgeStrategy
/// This purge strategy purges all files that have /// This purge strategy purges all files that have
/// exceeded a given age (given in seconds). /// exceeded a given age (given in seconds).

View File

@@ -49,6 +49,13 @@ private:
}; };
class Foundation_API NullRotateStrategy : public RotateStrategy
{
public:
bool mustRotate(LogFile* pFile);
};
template <class DT> template <class DT>
class RotateAtTimeStrategy: public RotateStrategy class RotateAtTimeStrategy: public RotateStrategy
/// The file is rotated at specified [day,][hour]:minute /// The file is rotated at specified [day,][hour]:minute

View File

@@ -161,6 +161,12 @@ ArchiveByNumberStrategy::~ArchiveByNumberStrategy()
} }
LogFile* ArchiveByNumberStrategy::open(LogFile* pFile)
{
return pFile;
}
LogFile* ArchiveByNumberStrategy::archive(LogFile* pFile) LogFile* ArchiveByNumberStrategy::archive(LogFile* pFile)
{ {
std::string basePath = pFile->path(); std::string basePath = pFile->path();

View File

@@ -45,9 +45,9 @@ FileChannel::FileChannel():
_flush(true), _flush(true),
_rotateOnOpen(false), _rotateOnOpen(false),
_pFile(0), _pFile(0),
_pRotateStrategy(0), _pRotateStrategy(new NullRotateStrategy()),
_pArchiveStrategy(new ArchiveByNumberStrategy), _pArchiveStrategy(new ArchiveByNumberStrategy),
_pPurgeStrategy(0) _pPurgeStrategy(new NullPurgeStrategy())
{ {
} }
@@ -59,9 +59,9 @@ FileChannel::FileChannel(const std::string& path):
_flush(true), _flush(true),
_rotateOnOpen(false), _rotateOnOpen(false),
_pFile(0), _pFile(0),
_pRotateStrategy(0), _pRotateStrategy(new NullRotateStrategy()),
_pArchiveStrategy(new ArchiveByNumberStrategy), _pArchiveStrategy(new ArchiveByNumberStrategy),
_pPurgeStrategy(0) _pPurgeStrategy(new NullPurgeStrategy())
{ {
} }
@@ -101,6 +101,8 @@ void FileChannel::open()
_pFile = new LogFile(_path); _pFile = new LogFile(_path);
} }
} }
_pFile = _pArchiveStrategy->open(_pFile);
} }
} }
@@ -120,7 +122,7 @@ void FileChannel::log(const Message& msg)
FastMutex::ScopedLock lock(_mutex); FastMutex::ScopedLock lock(_mutex);
if (_pRotateStrategy && _pArchiveStrategy && _pRotateStrategy->mustRotate(_pFile)) if (_pRotateStrategy->mustRotate(_pFile))
{ {
try 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 it = rotation.begin();
std::string::const_iterator end = rotation.end(); std::string::const_iterator end = rotation.end();
@@ -238,12 +240,12 @@ void FileChannel::setRotation(const std::string& rotation)
RotateStrategy* pStrategy = 0; RotateStrategy* pStrategy = 0;
if ((rotation.find(',') != std::string::npos) || (rotation.find(':') != std::string::npos)) if ((rotation.find(',') != std::string::npos) || (rotation.find(':') != std::string::npos))
{ {
if (_times == "utc") if (times == "utc")
pStrategy = new RotateAtTimeStrategy<DateTime>(rotation); pStrategy = new RotateAtTimeStrategy<DateTime>(rotation);
else if (_times == "local") else if (times == "local")
pStrategy = new RotateAtTimeStrategy<LocalDateTime>(rotation); pStrategy = new RotateAtTimeStrategy<LocalDateTime>(rotation);
else else
throw PropertyNotSupportedException("times", _times); throw PropertyNotSupportedException("times", times);
} }
else if (unit == "daily") else if (unit == "daily")
pStrategy = new RotateByIntervalStrategy(Timespan(1*Timespan::DAYS)); pStrategy = new RotateByIntervalStrategy(Timespan(1*Timespan::DAYS));
@@ -271,12 +273,57 @@ void FileChannel::setRotation(const std::string& rotation)
pStrategy = new RotateBySizeStrategy(n); pStrategy = new RotateBySizeStrategy(n);
else if (unit != "never") else if (unit != "never")
throw InvalidArgumentException("rotation", rotation); throw InvalidArgumentException("rotation", rotation);
return pStrategy;
}
void FileChannel::setRotationStrategy(RotateStrategy* strategy)
{
poco_check_ptr(strategy);
delete _pRotateStrategy; delete _pRotateStrategy;
_pRotateStrategy = pStrategy; _pRotateStrategy = strategy;
}
void FileChannel::setRotation(const std::string& rotation)
{
setRotationStrategy(createRotationStrategy(rotation, _times));
_rotation = rotation; _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) void FileChannel::setArchive(const std::string& archive)
{ {
ArchiveStrategy* pStrategy = 0; ArchiveStrategy* pStrategy = 0;
@@ -304,8 +351,7 @@ void FileChannel::setArchive(const std::string& archive)
void FileChannel::setCompress(const std::string& compress) void FileChannel::setCompress(const std::string& compress)
{ {
_compress = icompare(compress, "true") == 0; _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() 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")) if (value.empty() || 0 == icompare(value, "none"))
{ {
delete _pPurgeStrategy; delete _pPurgeStrategy;
_pPurgeStrategy = 0; _pPurgeStrategy = new NullPurgeStrategy();
_purgeAge = "none"; _purgeAge = "none";
return true; return true;
} }
@@ -394,6 +437,8 @@ int FileChannel::extractDigit(const std::string& value, std::string::const_itera
void FileChannel::setPurgeStrategy(PurgeStrategy* strategy) void FileChannel::setPurgeStrategy(PurgeStrategy* strategy)
{ {
poco_check_ptr(strategy);
delete _pPurgeStrategy; delete _pPurgeStrategy;
_pPurgeStrategy = strategy; _pPurgeStrategy = strategy;
} }

View File

@@ -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 // PurgeByAgeStrategy
// //

View File

@@ -38,6 +38,17 @@ RotateStrategy::~RotateStrategy()
} }
//
// NullRotateStrategy
//
bool NullRotateStrategy::mustRotate(LogFile* pFile)
{
return false;
}
// //
// RotateByIntervalStrategy // RotateByIntervalStrategy
// //

View File

@@ -26,6 +26,9 @@
#include "Poco/NumberFormatter.h" #include "Poco/NumberFormatter.h"
#include "Poco/DirectoryIterator.h" #include "Poco/DirectoryIterator.h"
#include "Poco/Exception.h" #include "Poco/Exception.h"
#include "Poco/RotateStrategy.h"
#include "Poco/ArchiveStrategy.h"
#include "Poco/PurgeStrategy.h"
#include <vector> #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() void FileChannelTest::testArchive()
{ {
std::string name = filename(); 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() void FileChannelTest::testCompress()
{ {
std::string name = filename(); 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() void FileChannelTest::setUp()
{ {
} }
@@ -642,11 +812,14 @@ CppUnit::Test* FileChannelTest::suite()
CppUnit_addLongTest(pSuite, FileChannelTest, testRotateAtTimeHourLocal); CppUnit_addLongTest(pSuite, FileChannelTest, testRotateAtTimeHourLocal);
CppUnit_addLongTest(pSuite, FileChannelTest, testRotateAtTimeMinUTC); CppUnit_addLongTest(pSuite, FileChannelTest, testRotateAtTimeMinUTC);
CppUnit_addLongTest(pSuite, FileChannelTest, testRotateAtTimeMinLocal); CppUnit_addLongTest(pSuite, FileChannelTest, testRotateAtTimeMinLocal);
CppUnit_addTest(pSuite, FileChannelTest, testRotateByStrategy);
CppUnit_addTest(pSuite, FileChannelTest, testArchive); CppUnit_addTest(pSuite, FileChannelTest, testArchive);
CppUnit_addTest(pSuite, FileChannelTest, testArchiveByStrategy);
CppUnit_addTest(pSuite, FileChannelTest, testCompress); CppUnit_addTest(pSuite, FileChannelTest, testCompress);
CppUnit_addLongTest(pSuite, FileChannelTest, testPurgeAge); CppUnit_addLongTest(pSuite, FileChannelTest, testPurgeAge);
CppUnit_addTest(pSuite, FileChannelTest, testPurgeCount); CppUnit_addTest(pSuite, FileChannelTest, testPurgeCount);
CppUnit_addTest(pSuite, FileChannelTest, testWrongPurgeOption); CppUnit_addTest(pSuite, FileChannelTest, testWrongPurgeOption);
CppUnit_addTest(pSuite, FileChannelTest, testPurgeByStrategy);
return pSuite; return pSuite;
} }

View File

@@ -39,11 +39,14 @@ public:
void testRotateAtTimeHourLocal(); void testRotateAtTimeHourLocal();
void testRotateAtTimeMinUTC(); void testRotateAtTimeMinUTC();
void testRotateAtTimeMinLocal(); void testRotateAtTimeMinLocal();
void testRotateByStrategy();
void testArchive(); void testArchive();
void testArchiveByStrategy();
void testCompress(); void testCompress();
void testPurgeAge(); void testPurgeAge();
void testPurgeCount(); void testPurgeCount();
void testWrongPurgeOption(); void testWrongPurgeOption();
void testPurgeByStrategy();
void setUp(); void setUp();
void tearDown(); void tearDown();