blob: ace7a1b58f32d2cf051f0ef1aef28df5b7a44009 [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2020 YADRO
#include "file_storage.hpp"
#include "zlib_file.hpp"
#include <set>
namespace fs = std::filesystem;
/** @brief File extension for log files. */
static const std::string fileExt = ".log.gz";
FileStorage::FileStorage(const std::string& path, const std::string& prefix,
size_t maxFiles) :
outDir(path),
filePrefix(prefix), filesLimit(maxFiles)
{
// Check path
if (!outDir.is_absolute())
{
throw std::invalid_argument("Path must be absolute");
}
fs::create_directories(outDir);
// Normalize file name prefix
if (filePrefix.empty())
{
filePrefix = "host";
}
}
std::string FileStorage::save(const LogBuffer& buf) const
{
if (buf.empty())
{
return std::string(); // Buffer is empty, nothing to save
}
const std::string fileName = newFile();
ZlibFile logFile(fileName);
// Write full datetime stamp as the first record
tm tmLocal;
localtime_r(&buf.begin()->timeStamp, &tmLocal);
char tmText[20]; // asciiz for YYYY-MM-DD HH:MM:SS
strftime(tmText, sizeof(tmText), "%F %T", &tmLocal);
std::string titleMsg = ">>> Log collection started at ";
titleMsg += tmText;
logFile.write(tmLocal, titleMsg);
// Write messages
for (const auto& msg : buf)
{
localtime_r(&msg.timeStamp, &tmLocal);
logFile.write(tmLocal, msg.text);
}
logFile.close();
rotate();
return fileName;
}
std::string FileStorage::newFile() const
{
// Prepare directory
fs::create_directories(outDir);
// Construct log file name: {prefix}_{timestamp}[_N].{ext}
std::string fileName = outDir / (filePrefix + '_');
time_t tmCurrent;
time(&tmCurrent);
tm tmLocal;
localtime_r(&tmCurrent, &tmLocal);
char tmText[16]; // asciiz for YYYYMMDD_HHMMSS
strftime(tmText, sizeof(tmText), "%Y%m%d_%H%M%S", &tmLocal);
fileName += tmText;
// Handle duplicate files
std::string dupPostfix;
size_t dupCounter = 0;
while (fs::exists(fileName + dupPostfix + fileExt))
{
dupPostfix = '_' + std::to_string(++dupCounter);
}
fileName += dupPostfix;
fileName += fileExt;
return fileName;
}
void FileStorage::rotate() const
{
if (!filesLimit)
{
return; // Unlimited
}
// Get file list to ordered set
std::set<std::string> logFiles;
for (const auto& file : fs::directory_iterator(outDir))
{
if (!fs::is_regular_file(file))
{
continue;
}
const std::string fileName = file.path().filename();
const size_t minFileNameLen = filePrefix.length() +
15 + // time stamp YYYYMMDD_HHMMSS
fileExt.length();
if (fileName.length() < minFileNameLen)
{
continue;
}
if (fileName.compare(fileName.length() - fileExt.length(),
fileExt.length(), fileExt))
{
continue;
}
const std::string fullPrefix = filePrefix + '_';
if (fileName.compare(0, fullPrefix.length(), fullPrefix))
{
continue;
}
logFiles.insert(fileName);
}
// Log file has a name with a timestamp generated. The sorted set contains
// the oldest file on the top, remove them.
if (logFiles.size() > filesLimit)
{
size_t removeCount = logFiles.size() - filesLimit;
for (const auto& fileName : logFiles)
{
fs::remove(outDir / fileName);
if (!--removeCount)
{
break;
}
}
}
}