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