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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 322 additions and 22 deletions

View File

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

View File

@ -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;

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
/// This purge strategy purges all files that have
/// 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>
class RotateAtTimeStrategy: public RotateStrategy
/// 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)
{
std::string basePath = pFile->path();

View File

@ -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;
}

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

View File

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

View File

@ -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;
}

View File

@ -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();