unilink  0.4.3
A simple C++ library for unified async communication
log_rotation.cc
Go to the documentation of this file.
1 /*
2  * Copyright 2025 Jinwoo Sung
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
18 
19 #include <algorithm>
20 #include <iomanip>
21 #include <regex>
22 #include <sstream>
23 
24 namespace unilink {
25 namespace diagnostics {
26 
27 LogRotation::LogRotation(const LogRotationConfig& config) : config_(config) {}
28 
29 bool LogRotation::should_rotate(const std::string& filepath) const {
30  std::lock_guard<std::mutex> lock(mutex_);
31 
32  if (!std::filesystem::exists(filepath)) {
33  return false;
34  }
35 
36  size_t file_size = get_file_size(filepath);
37  return file_size >= config_.max_file_size_bytes;
38 }
39 
40 std::string LogRotation::rotate(const std::string& filepath) {
41  std::lock_guard<std::mutex> lock(mutex_);
42 
43  if (!std::filesystem::exists(filepath)) {
44  return filepath; // File doesn't exist, no rotation needed
45  }
46 
47  // Get the next available file path
48  std::string new_filepath = get_next_file_path(filepath);
49 
50  try {
51  // Rename current file to new filepath
52  std::filesystem::rename(filepath, new_filepath);
53 
54  // Clean up old files
55  cleanup_old_files(filepath);
56 
57  return filepath; // Return original path for new log file
58  } catch (const std::filesystem::filesystem_error&) {
59  // If rename fails, return original path
60  return filepath;
61  }
62 }
63 
64 void LogRotation::cleanup_old_files(const std::string& base_filepath) {
65  try {
66  std::vector<std::string> log_files = get_log_files(base_filepath);
67 
68  // Sort by modification time (newest first)
69  sort_files_by_time(log_files);
70 
71  // Remove files beyond the limit
72  if (log_files.size() > config_.max_files) {
73  for (size_t i = config_.max_files; i < log_files.size(); ++i) {
74  try {
75  std::filesystem::remove(log_files[i]);
76  } catch (const std::filesystem::filesystem_error&) {
77  // Ignore removal errors
78  }
79  }
80  }
81  } catch (const std::exception&) {
82  // Ignore cleanup errors
83  }
84 }
85 
86 std::string LogRotation::get_next_file_path(const std::string& base_filepath) const {
87  std::string base_name = get_base_filename(base_filepath);
88  std::string directory = get_directory(base_filepath);
89 
90  // Find the highest index
91  int max_index = -1;
92  std::vector<std::string> log_files = get_log_files(base_filepath);
93 
94  for (const auto& file : log_files) {
95  std::string filename = std::filesystem::path(file).filename().string();
96  int index = get_file_index(filename);
97  if (index > max_index) {
98  max_index = index;
99  }
100  }
101 
102  // Generate next filename
103  int next_index = max_index + 1;
104  std::string next_filename = generate_filename(base_name, next_index);
105 
106  return directory + "/" + next_filename;
107 }
108 
110  std::lock_guard<std::mutex> lock(mutex_);
111  config_ = config;
112 }
113 
114 size_t LogRotation::get_file_size(const std::string& filepath) {
115  try {
116  if (std::filesystem::exists(filepath)) {
117  return std::filesystem::file_size(filepath);
118  }
119  } catch (const std::filesystem::filesystem_error&) {
120  // Return 0 if file doesn't exist or can't be accessed
121  }
122  return 0;
123 }
124 
125 std::vector<std::string> LogRotation::get_log_files(const std::string& base_filepath) {
126  std::vector<std::string> log_files;
127 
128  try {
129  std::string base_name = std::filesystem::path(base_filepath).stem().string();
130  std::string directory = std::filesystem::path(base_filepath).parent_path().string();
131 
132  if (directory.empty()) {
133  directory = ".";
134  }
135 
136  // Create regex pattern to match log files
137  std::regex pattern(base_name + "\\.\\d+\\.log");
138 
139  for (const auto& entry : std::filesystem::directory_iterator(directory)) {
140  if (entry.is_regular_file()) {
141  std::string filename = entry.path().filename().string();
142  if (std::regex_match(filename, pattern)) {
143  log_files.push_back(entry.path().string());
144  }
145  }
146  }
147  } catch (const std::filesystem::filesystem_error&) {
148  // Return empty vector if directory access fails
149  }
150 
151  return log_files;
152 }
153 
154 std::string LogRotation::get_base_filename(const std::string& filepath) const {
155  std::filesystem::path path(filepath);
156  return path.stem().string();
157 }
158 
159 std::string LogRotation::get_directory(const std::string& filepath) const {
160  std::filesystem::path path(filepath);
161  std::string dir = path.parent_path().string();
162  return dir.empty() ? "." : dir;
163 }
164 
165 int LogRotation::get_file_index(const std::string& filename) const {
166  // Extract index from filename like "app.1.log"
167  std::regex pattern(R"((\d+)\.log$)");
168  std::smatch match;
169 
170  if (std::regex_search(filename, match, pattern)) {
171  return std::stoi(match[1].str());
172  }
173 
174  return -1;
175 }
176 
177 std::string LogRotation::generate_filename(const std::string& base_name, int index) const {
178  std::ostringstream oss;
179  oss << base_name << "." << index << ".log";
180  return oss.str();
181 }
182 
183 void LogRotation::sort_files_by_time(std::vector<std::string>& files) const {
184  std::sort(files.begin(), files.end(), [](const std::string& a, const std::string& b) {
185  try {
186  auto time_a = std::filesystem::last_write_time(a);
187  auto time_b = std::filesystem::last_write_time(b);
188  return time_a > time_b; // Newest first
189  } catch (const std::filesystem::filesystem_error&) {
190  return false;
191  }
192  });
193 }
194 
195 } // namespace diagnostics
196 } // namespace unilink