|
1 | 1 | #pragma once |
2 | 2 | #include <fstream> |
| 3 | +#include <atomic> |
3 | 4 | #include <mutex> |
4 | 5 | #include <string> |
| 6 | +#include <algorithm> |
| 7 | +#include <cctype> |
5 | 8 | #include <iostream> |
6 | 9 |
|
7 | 10 | #ifndef WIN32_LEAN_AND_MEAN |
|
14 | 17 | #include <sstream> |
15 | 18 |
|
16 | 19 | namespace Core { |
| 20 | + // 日志等级(用于控制输出粒度:默认 Info;需要更细粒度排障时可切到 Debug) |
| 21 | + enum class LogLevel : int { |
| 22 | + Debug = 0, |
| 23 | + Info = 1, |
| 24 | + Warn = 2, |
| 25 | + Error = 3, |
| 26 | + }; |
| 27 | + |
17 | 28 | class Logger { |
18 | 29 | private: |
| 30 | + // ========== 日志等级控制 ========== |
| 31 | + // 设计意图:默认 Info(更克制),现场需要时可切到 Debug;同时提供可配置降级能力以降低性能开销。 |
| 32 | + static std::atomic<int>& LevelStorage() { |
| 33 | + static std::atomic<int> s_level{static_cast<int>(LogLevel::Info)}; |
| 34 | + return s_level; |
| 35 | + } |
| 36 | + |
| 37 | + static bool TryParseLevelFromString(const std::string& input, LogLevel* out) { |
| 38 | + if (!out) return false; |
| 39 | + |
| 40 | + // 去掉首尾空白,并统一转小写(配置中常用 debug/info/warn/error) |
| 41 | + std::string s = input; |
| 42 | + auto notSpace = [](unsigned char c) { return !std::isspace(c); }; |
| 43 | + s.erase(s.begin(), std::find_if(s.begin(), s.end(), notSpace)); |
| 44 | + s.erase(std::find_if(s.rbegin(), s.rend(), notSpace).base(), s.end()); |
| 45 | + std::transform(s.begin(), s.end(), s.begin(), |
| 46 | + [](unsigned char c) { return (char)std::tolower(c); }); |
| 47 | + |
| 48 | + if (s == "debug" || s == "d" || s == "trace" || s == "verbose" || s == "调试") { |
| 49 | + *out = LogLevel::Debug; |
| 50 | + return true; |
| 51 | + } |
| 52 | + if (s == "info" || s == "i" || s == "信息") { |
| 53 | + *out = LogLevel::Info; |
| 54 | + return true; |
| 55 | + } |
| 56 | + if (s == "warn" || s == "warning" || s == "w" || s == "警告") { |
| 57 | + *out = LogLevel::Warn; |
| 58 | + return true; |
| 59 | + } |
| 60 | + if (s == "error" || s == "err" || s == "e" || s == "错误") { |
| 61 | + *out = LogLevel::Error; |
| 62 | + return true; |
| 63 | + } |
| 64 | + return false; |
| 65 | + } |
| 66 | + |
| 67 | + // ========== 跨进程互斥(用于多进程注入场景下的日志一致性) ========== |
| 68 | + // 设计意图:序列化“检查大小→必要时截断→写入”这一段,避免多进程互相截断或写入交错。 |
| 69 | + static HANDLE GetCrossProcessLogMutex() { |
| 70 | + static HANDLE s_mutex = NULL; |
| 71 | + static std::once_flag s_once; |
| 72 | + std::call_once(s_once, []() { |
| 73 | + // 使用 Local\ 命名空间:对普通桌面进程权限更稳 |
| 74 | + s_mutex = CreateMutexA(NULL, FALSE, "Local\\AntigravityProxy_LogFileMutex"); |
| 75 | + }); |
| 76 | + return s_mutex; |
| 77 | + } |
| 78 | + |
19 | 79 | // ========== 日志目录相关函数 ========== |
20 | 80 |
|
21 | 81 | // 获取 DLL 所在目录(用于定位日志目录) |
@@ -138,6 +198,14 @@ namespace Core { |
138 | 198 | return size >= maxBytes; |
139 | 199 | } |
140 | 200 |
|
| 201 | + static ULONGLONG GetFileSizeBytes(const std::string& path) { |
| 202 | + WIN32_FILE_ATTRIBUTE_DATA data{}; |
| 203 | + if (!GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &data)) { |
| 204 | + return 0; |
| 205 | + } |
| 206 | + return (static_cast<ULONGLONG>(data.nFileSizeHigh) << 32) | data.nFileSizeLow; |
| 207 | + } |
| 208 | + |
141 | 209 | // 清理旧日志文件,只保留当天的日志 |
142 | 210 | static void CleanupOldLogs(const std::string& todayLog) { |
143 | 211 | std::string logDir = GetLogDirectory(); |
@@ -175,57 +243,100 @@ namespace Core { |
175 | 243 | } |
176 | 244 |
|
177 | 245 | static void WriteToFile(const std::string& message) { |
178 | | - static std::mutex mtx; |
179 | | - std::lock_guard<std::mutex> lock(mtx); |
180 | 246 | // 按日期写日志并清理旧文件,避免历史日志堆积 |
181 | 247 | static std::string s_todayLog; |
182 | | - static ULONGLONG s_lastCheckTick = 0; |
183 | | - static bool s_dropForToday = false; |
184 | | - static const ULONGLONG kMaxLogBytes = 100ull * 1024 * 1024; // 100MB |
185 | | - static const ULONGLONG kCheckIntervalMs = 60ull * 60 * 1000; // 1 小时 |
| 248 | + // 需求:单文件 10MB 达到即覆盖写入(不轮转、不备份) |
| 249 | + static const ULONGLONG kMaxLogBytes = 10ull * 1024 * 1024; // 10MB |
| 250 | + |
| 251 | + // 多进程注入场景:使用跨进程互斥量保证“检查+截断+写入”的原子性 |
| 252 | + HANDLE hMutex = GetCrossProcessLogMutex(); |
| 253 | + DWORD waitRc = WAIT_FAILED; |
| 254 | + if (hMutex) { |
| 255 | + waitRc = WaitForSingleObject(hMutex, INFINITE); |
| 256 | + } |
| 257 | + const bool locked = (hMutex != NULL) && (waitRc == WAIT_OBJECT_0 || waitRc == WAIT_ABANDONED); |
| 258 | + |
| 259 | + // 如果跨进程互斥不可用,退化为进程内互斥,保证不崩溃(但多进程一致性会弱一些) |
| 260 | + static std::mutex s_fallbackMtx; |
| 261 | + std::unique_lock<std::mutex> fallbackLock; |
| 262 | + if (!locked) { |
| 263 | + fallbackLock = std::unique_lock<std::mutex>(s_fallbackMtx); |
| 264 | + } |
| 265 | + |
186 | 266 | std::string todayLog = GetTodayLogName(); |
187 | 267 | if (s_todayLog != todayLog) { |
188 | 268 | s_todayLog = todayLog; |
189 | | - s_lastCheckTick = 0; |
190 | | - s_dropForToday = false; |
191 | 269 | CleanupOldLogs(s_todayLog); |
192 | 270 | } |
193 | | - if (s_dropForToday) { |
194 | | - return; |
195 | | - } |
196 | | - ULONGLONG nowTick = GetTickCount64(); |
197 | | - if (s_lastCheckTick == 0 || nowTick - s_lastCheckTick >= kCheckIntervalMs) { |
198 | | - s_lastCheckTick = nowTick; |
199 | | - if (IsLogOverLimit(s_todayLog, kMaxLogBytes)) { |
200 | | - // 当天日志超过上限后直接丢弃,次日恢复 |
201 | | - s_dropForToday = true; |
202 | | - return; |
203 | | - } |
| 271 | + |
| 272 | + // 判断本次写入是否会超过上限;超过则直接截断覆盖写入 |
| 273 | + const ULONGLONG currentSize = GetFileSizeBytes(s_todayLog); |
| 274 | + const ULONGLONG appendBytes = static_cast<ULONGLONG>(message.size() + 1); // + '\n' |
| 275 | + const bool needTruncate = (currentSize > 0 && (currentSize + appendBytes) > kMaxLogBytes); |
| 276 | + |
| 277 | + std::ofstream logFile; |
| 278 | + if (needTruncate) { |
| 279 | + logFile.open(s_todayLog, std::ios::out | std::ios::trunc); |
| 280 | + } else { |
| 281 | + logFile.open(s_todayLog, std::ios::out | std::ios::app); |
204 | 282 | } |
205 | | - std::ofstream logFile(s_todayLog, std::ios::app); |
206 | 283 | if (logFile.is_open()) { |
207 | 284 | logFile << message << "\n"; |
208 | 285 | } |
| 286 | + |
| 287 | + if (locked) { |
| 288 | + ReleaseMutex(hMutex); |
| 289 | + } |
209 | 290 | } |
210 | 291 |
|
211 | 292 | public: |
| 293 | + // 判断某个等级的日志是否会输出(用于调用方做“懒构造字符串”,减少性能开销) |
| 294 | + static bool IsEnabled(LogLevel level) { |
| 295 | + const int threshold = LevelStorage().load(std::memory_order_relaxed); |
| 296 | + return static_cast<int>(level) >= threshold; |
| 297 | + } |
| 298 | + |
| 299 | + static LogLevel GetLevel() { |
| 300 | + return static_cast<LogLevel>(LevelStorage().load(std::memory_order_relaxed)); |
| 301 | + } |
| 302 | + |
| 303 | + static void SetLevel(LogLevel level) { |
| 304 | + LevelStorage().store(static_cast<int>(level), std::memory_order_relaxed); |
| 305 | + } |
| 306 | + |
| 307 | + // 从字符串设置日志等级;返回是否识别成功(不识别则不修改当前等级) |
| 308 | + static bool SetLevelFromString(const std::string& levelStr) { |
| 309 | + LogLevel parsed; |
| 310 | + if (!TryParseLevelFromString(levelStr, &parsed)) { |
| 311 | + return false; |
| 312 | + } |
| 313 | + SetLevel(parsed); |
| 314 | + return true; |
| 315 | + } |
| 316 | + |
212 | 317 | static void Log(const std::string& message) { |
| 318 | + // 将无等级的 Log 视为 Info 级别,确保可被 log_level 控制 |
| 319 | + if (!IsEnabled(LogLevel::Info)) return; |
213 | 320 | WriteToFile("[" + GetTimestamp() + "] " + GetPidTidPrefix() + " " + message); |
214 | 321 | } |
215 | 322 |
|
216 | 323 | static void Error(const std::string& message) { |
| 324 | + if (!IsEnabled(LogLevel::Error)) return; |
217 | 325 | WriteToFile("[" + GetTimestamp() + "] " + GetPidTidPrefix() + " [错误] " + message); |
218 | 326 | } |
219 | 327 |
|
220 | 328 | static void Info(const std::string& message) { |
| 329 | + if (!IsEnabled(LogLevel::Info)) return; |
221 | 330 | WriteToFile("[" + GetTimestamp() + "] " + GetPidTidPrefix() + " [信息] " + message); |
222 | 331 | } |
223 | 332 |
|
224 | 333 | static void Warn(const std::string& message) { |
| 334 | + if (!IsEnabled(LogLevel::Warn)) return; |
225 | 335 | WriteToFile("[" + GetTimestamp() + "] " + GetPidTidPrefix() + " [警告] " + message); |
226 | 336 | } |
227 | 337 |
|
228 | 338 | static void Debug(const std::string& message) { |
| 339 | + if (!IsEnabled(LogLevel::Debug)) return; |
229 | 340 | WriteToFile("[" + GetTimestamp() + "] " + GetPidTidPrefix() + " [调试] " + message); |
230 | 341 | } |
231 | 342 | }; |
|
0 commit comments