Skip to content

Commit 60fb0a0

Browse files
authored
Support setting max files in AsyncFileLogger. (#259)
1 parent 1eebee3 commit 60fb0a0

File tree

2 files changed

+142
-5
lines changed

2 files changed

+142
-5
lines changed

trantor/utils/AsyncFileLogger.cc

Lines changed: 121 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@
1616
#include <trantor/utils/Utilities.h>
1717
#ifndef _WIN32
1818
#include <unistd.h>
19+
#include <dirent.h>
20+
#include <sys/stat.h>
1921
#ifdef __linux__
2022
#include <sys/prctl.h>
2123
#endif
2224
#else
2325
#include <Windows.h>
2426
#endif
2527
#include <string.h>
28+
#include <algorithm>
2629
#include <iostream>
2730
#include <functional>
2831
#include <chrono>
@@ -119,8 +122,12 @@ void AsyncFileLogger::writeLogToFile(const StringPtr buf)
119122
{
120123
if (!loggerFilePtr_)
121124
{
122-
loggerFilePtr_ = std::unique_ptr<LoggerFile>(new LoggerFile(
123-
filePath_, fileBaseName_, fileExtName_, switchOnLimitOnly_));
125+
loggerFilePtr_ =
126+
std::unique_ptr<LoggerFile>(new LoggerFile(filePath_,
127+
fileBaseName_,
128+
fileExtName_,
129+
switchOnLimitOnly_,
130+
maxFiles_));
124131
}
125132
loggerFilePtr_->writeLog(buf);
126133
if (loggerFilePtr_->getLength() > sizeLimit_)
@@ -178,14 +185,21 @@ void AsyncFileLogger::startLogging()
178185
AsyncFileLogger::LoggerFile::LoggerFile(const std::string &filePath,
179186
const std::string &fileBaseName,
180187
const std::string &fileExtName,
181-
bool switchOnLimitOnly)
188+
bool switchOnLimitOnly,
189+
size_t maxFiles)
182190
: creationDate_(Date::date()),
183191
filePath_(filePath),
184192
fileBaseName_(fileBaseName),
185193
fileExtName_(fileExtName),
186-
switchOnLimitOnly_(switchOnLimitOnly)
194+
switchOnLimitOnly_(switchOnLimitOnly),
195+
maxFiles_(maxFiles)
187196
{
188197
open();
198+
199+
if (maxFiles_ > 0)
200+
{
201+
initFilenameQueue();
202+
}
189203
}
190204

191205
/**
@@ -252,6 +266,7 @@ void AsyncFileLogger::LoggerFile::switchLog(bool openNewOne)
252266
".%06llu",
253267
static_cast<long long unsigned int>(fileSeq_ % 1000000));
254268
++fileSeq_;
269+
// NOTE: Remember to update initFilenameQueue() if name format changes
255270
std::string newName =
256271
filePath_ + fileBaseName_ + "." +
257272
creationDate_.toCustomedFormattedString("%y%m%d-%H%M%S") +
@@ -264,6 +279,14 @@ void AsyncFileLogger::LoggerFile::switchLog(bool openNewOne)
264279
auto wNewName{utils::toNativePath(newName)};
265280
_wrename(wFullName.c_str(), wNewName.c_str());
266281
#endif
282+
if (maxFiles_ > 0)
283+
{
284+
filenameQueue_.push_back(newName);
285+
if (filenameQueue_.size() > maxFiles_)
286+
{
287+
deleteOldFiles();
288+
}
289+
}
267290
if (openNewOne)
268291
open(); // continue logging with base name until next renaming will
269292
// be required
@@ -278,6 +301,100 @@ AsyncFileLogger::LoggerFile::~LoggerFile()
278301
fclose(fp_);
279302
}
280303

304+
void AsyncFileLogger::LoggerFile::initFilenameQueue()
305+
{
306+
if (maxFiles_ <= 0)
307+
{
308+
return;
309+
}
310+
311+
// walk through the directory and file all files
312+
#if !defined(_WIN32) || defined(__MINGW32__)
313+
DIR *dp;
314+
struct dirent *dirp;
315+
struct stat st;
316+
317+
if ((dp = opendir(filePath_.c_str())) == nullptr)
318+
{
319+
fprintf(stderr,
320+
"Can't open dir %s: %s\n",
321+
filePath_.c_str(),
322+
strerror_tl(errno));
323+
return;
324+
}
325+
326+
while ((dirp = readdir(dp)) != nullptr)
327+
{
328+
std::string name = dirp->d_name;
329+
// <base>.yymmdd-hhmmss.000000<ext>
330+
// NOTE: magic number 21: the length of middle part of generated name
331+
if (name.size() != fileBaseName_.size() + 21 + fileExtName_.size() ||
332+
name.compare(0, fileBaseName_.size(), fileBaseName_) != 0 ||
333+
name.compare(name.size() - fileExtName_.size(),
334+
fileExtName_.size(),
335+
fileExtName_) != 0)
336+
{
337+
continue;
338+
}
339+
std::string fullname = filePath_ + name;
340+
if (stat(fullname.c_str(), &st) == -1)
341+
{
342+
fprintf(stderr,
343+
"Can't stat file %s: %s\n",
344+
fullname.c_str(),
345+
strerror_tl(errno));
346+
continue;
347+
}
348+
if (!S_ISREG(st.st_mode))
349+
{
350+
continue;
351+
}
352+
filenameQueue_.push_back(fullname);
353+
std::push_heap(filenameQueue_.begin(),
354+
filenameQueue_.end(),
355+
std::greater<>());
356+
if (filenameQueue_.size() > maxFiles_)
357+
{
358+
std::pop_heap(filenameQueue_.begin(),
359+
filenameQueue_.end(),
360+
std::greater<>());
361+
auto fileToRemove = std::move(filenameQueue_.back());
362+
filenameQueue_.pop_back();
363+
remove(fileToRemove.c_str());
364+
}
365+
}
366+
closedir(dp);
367+
#else
368+
// TODO: windows implementation
369+
#endif
370+
371+
std::sort(filenameQueue_.begin(), filenameQueue_.end(), std::less<>());
372+
}
373+
374+
void AsyncFileLogger::LoggerFile::deleteOldFiles()
375+
{
376+
while (filenameQueue_.size() > maxFiles_)
377+
{
378+
std::string filename = std::move(filenameQueue_.front());
379+
filenameQueue_.pop_front();
380+
381+
#if !defined(_WIN32) || defined(__MINGW32__)
382+
int r = remove(filename.c_str());
383+
#else
384+
// Convert UTF-8 file to UCS-2
385+
auto wName{utils::toNativePath(filename)};
386+
int r = _wremove(wName.c_str());
387+
#endif
388+
if (r != 0)
389+
{
390+
fprintf(stderr,
391+
"Failed to remove file %s: %s\n",
392+
filename.c_str(),
393+
strerror_tl(errno));
394+
}
395+
}
396+
}
397+
281398
void AsyncFileLogger::swapBuffer()
282399
{
283400
writeBuffers_.push(logBufferPtr_);

trantor/utils/AsyncFileLogger.h

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ class TRANTOR_EXPORT AsyncFileLogger : NonCopyable
6969
sizeLimit_ = limit;
7070
}
7171

72+
/**
73+
* @brief Set the max number of log files. When the number exceeds the
74+
* limit, the oldest log file will be deleted.
75+
*
76+
* @param limit
77+
*/
78+
void setMaxFiles(size_t maxFiles)
79+
{
80+
maxFiles_ = maxFiles;
81+
}
82+
7283
/**
7384
* @brief Set whether to switch the log file when the AsyncFileLogger object
7485
* is destroyed. If this flag is set to true, the log file is not switched
@@ -121,14 +132,16 @@ class TRANTOR_EXPORT AsyncFileLogger : NonCopyable
121132
uint64_t sizeLimit_{20 * 1024 * 1024};
122133
bool switchOnLimitOnly_{false}; // by default false, will generate new
123134
// file name on each destroy.
135+
size_t maxFiles_{0};
124136

125137
class LoggerFile : NonCopyable
126138
{
127139
public:
128140
LoggerFile(const std::string &filePath,
129141
const std::string &fileBaseName,
130142
const std::string &fileExtName,
131-
bool switchOnLimitOnly = false);
143+
bool switchOnLimitOnly = false,
144+
size_t maxFiles = 0);
132145
~LoggerFile();
133146
void writeLog(const StringPtr buf);
134147
void open();
@@ -141,6 +154,9 @@ class TRANTOR_EXPORT AsyncFileLogger : NonCopyable
141154
void flush();
142155

143156
protected:
157+
void initFilenameQueue();
158+
void deleteOldFiles();
159+
144160
FILE *fp_{nullptr};
145161
Date creationDate_;
146162
std::string fileFullName_;
@@ -150,6 +166,10 @@ class TRANTOR_EXPORT AsyncFileLogger : NonCopyable
150166
static uint64_t fileSeq_;
151167
bool switchOnLimitOnly_{false}; // by default false, will generate new
152168
// file name on each destroy
169+
170+
size_t maxFiles_{0};
171+
// store generated filenames
172+
std::deque<std::string> filenameQueue_;
153173
};
154174
std::unique_ptr<LoggerFile> loggerFilePtr_;
155175

0 commit comments

Comments
 (0)