blob: cf35ece94dda20f39b96ec63fdf2eb3781f516bd [file] [log] [blame]
Artem Senicheve8837d52020-06-07 11:59:04 +03001// SPDX-License-Identifier: Apache-2.0
2// Copyright (C) 2020 YADRO
3
4#include "file_storage.hpp"
5
6#include "zlib_file.hpp"
7
8#include <set>
9
10namespace fs = std::filesystem;
11
12/** @brief File extension for log files. */
13static const std::string fileExt = ".log.gz";
14
15FileStorage::FileStorage(const std::string& path, const std::string& prefix,
16 size_t maxFiles) :
Patrick Williams87c333e2024-08-16 15:20:11 -040017 outDir(path), filePrefix(prefix), filesLimit(maxFiles)
Artem Senicheve8837d52020-06-07 11:59:04 +030018{
19 // Check path
20 if (!outDir.is_absolute())
21 {
22 throw std::invalid_argument("Path must be absolute");
23 }
24 fs::create_directories(outDir);
25
26 // Normalize file name prefix
27 if (filePrefix.empty())
28 {
29 filePrefix = "host";
30 }
31}
32
33std::string FileStorage::save(const LogBuffer& buf) const
34{
35 if (buf.empty())
36 {
37 return std::string(); // Buffer is empty, nothing to save
38 }
39
40 const std::string fileName = newFile();
41 ZlibFile logFile(fileName);
42
43 // Write full datetime stamp as the first record
44 tm tmLocal;
45 localtime_r(&buf.begin()->timeStamp, &tmLocal);
46 char tmText[20]; // asciiz for YYYY-MM-DD HH:MM:SS
47 strftime(tmText, sizeof(tmText), "%F %T", &tmLocal);
48 std::string titleMsg = ">>> Log collection started at ";
49 titleMsg += tmText;
50 logFile.write(tmLocal, titleMsg);
51
52 // Write messages
53 for (const auto& msg : buf)
54 {
55 localtime_r(&msg.timeStamp, &tmLocal);
56 logFile.write(tmLocal, msg.text);
57 }
58
59 logFile.close();
60
61 rotate();
62
63 return fileName;
64}
65
66std::string FileStorage::newFile() const
67{
68 // Prepare directory
69 fs::create_directories(outDir);
70
71 // Construct log file name: {prefix}_{timestamp}[_N].{ext}
72 std::string fileName = outDir / (filePrefix + '_');
73
74 time_t tmCurrent;
75 time(&tmCurrent);
76 tm tmLocal;
77 localtime_r(&tmCurrent, &tmLocal);
78 char tmText[16]; // asciiz for YYYYMMDD_HHMMSS
79 strftime(tmText, sizeof(tmText), "%Y%m%d_%H%M%S", &tmLocal);
80 fileName += tmText;
81
82 // Handle duplicate files
83 std::string dupPostfix;
84 size_t dupCounter = 0;
85 while (fs::exists(fileName + dupPostfix + fileExt))
86 {
87 dupPostfix = '_' + std::to_string(++dupCounter);
88 }
89 fileName += dupPostfix;
90 fileName += fileExt;
91
92 return fileName;
93}
94
95void FileStorage::rotate() const
96{
97 if (!filesLimit)
98 {
99 return; // Unlimited
100 }
101
102 // Get file list to ordered set
103 std::set<std::string> logFiles;
104 for (const auto& file : fs::directory_iterator(outDir))
105 {
106 if (!fs::is_regular_file(file))
107 {
108 continue;
109 }
110 const std::string fileName = file.path().filename();
111
Patrick Williams87c333e2024-08-16 15:20:11 -0400112 const size_t minFileNameLen =
113 filePrefix.length() + 15 + // time stamp YYYYMMDD_HHMMSS
114 fileExt.length();
Artem Senicheve8837d52020-06-07 11:59:04 +0300115 if (fileName.length() < minFileNameLen)
116 {
117 continue;
118 }
119
120 if (fileName.compare(fileName.length() - fileExt.length(),
121 fileExt.length(), fileExt))
122 {
123 continue;
124 }
125
126 const std::string fullPrefix = filePrefix + '_';
127 if (fileName.compare(0, fullPrefix.length(), fullPrefix))
128 {
129 continue;
130 }
131
132 logFiles.insert(fileName);
133 }
134
135 // Log file has a name with a timestamp generated. The sorted set contains
136 // the oldest file on the top, remove them.
137 if (logFiles.size() > filesLimit)
138 {
139 size_t removeCount = logFiles.size() - filesLimit;
140 for (const auto& fileName : logFiles)
141 {
142 fs::remove(outDir / fileName);
143 if (!--removeCount)
144 {
145 break;
146 }
147 }
148 }
149}