361 lines
10 KiB
C++
361 lines
10 KiB
C++
#pragma once
|
||
#ifdef _MSC_VER
|
||
#pragma warning(disable: 4996)
|
||
#endif
|
||
|
||
#ifdef _WIN32
|
||
#ifdef _WINDOWS
|
||
#include <afxwin.h>
|
||
#else
|
||
#include <windows.h>
|
||
#endif
|
||
#include "skCrypter.h"
|
||
#else
|
||
#include <unistd.h>
|
||
#include <sys/types.h>
|
||
#include <sys/stat.h>
|
||
#endif
|
||
|
||
#include <iostream>
|
||
#include <fstream>
|
||
#include <mutex>
|
||
#include <queue>
|
||
#include <string>
|
||
#include <thread>
|
||
#include <condition_variable>
|
||
#include <atomic>
|
||
#include <chrono>
|
||
#include <sstream>
|
||
#include <cstdarg>
|
||
#include <iomanip>
|
||
#include <algorithm>
|
||
|
||
|
||
inline bool stringToBool(const std::string& str)
|
||
{
|
||
std::string lower = str;
|
||
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
|
||
return (lower == "true" || lower == "1");
|
||
}
|
||
|
||
class Logger
|
||
{
|
||
public:
|
||
enum LogLevel {
|
||
InfoLevel, WarningLevel, ErrorLevel
|
||
};
|
||
|
||
// 单例模式
|
||
static Logger& getInstance()
|
||
{
|
||
static Logger instance;
|
||
if (instance.pid.empty()) {
|
||
#ifdef _WIN32
|
||
char buf[16] = {};
|
||
sprintf_s(buf, "%d", GetCurrentProcessId());
|
||
instance.pid = buf;
|
||
// SYSTEM | C:\Windows\Temp
|
||
// 普通用户 | C:\Users\用户名\AppData\Local\Temp
|
||
char logPath[MAX_PATH] = { 0 };
|
||
GetEnvironmentVariableA("TEMP", logPath, MAX_PATH);
|
||
instance.InitLogFile(logPath, instance.pid);
|
||
#ifdef _WINDOWS
|
||
instance.enable = true; // 主控日志默认打开
|
||
#else
|
||
char var[32] = {};
|
||
const char* name = skCrypt("ENABLE_LOG");
|
||
DWORD size = GetEnvironmentVariableA(name, var, sizeof(var));
|
||
instance.enable = stringToBool(var);
|
||
instance.log("logger.h", __LINE__, "GetEnvironmentVariable: %s=%s\n", name, var);
|
||
#endif
|
||
#else // Linux
|
||
char buf[16] = {};
|
||
snprintf(buf, sizeof(buf), "%d", (int)getpid());
|
||
instance.pid = buf;
|
||
// ~/.config/ghost/ 目录写日志
|
||
std::string logDir;
|
||
const char* xdg = getenv("XDG_CONFIG_HOME");
|
||
if (xdg && xdg[0]) {
|
||
logDir = std::string(xdg) + "/ghost";
|
||
} else {
|
||
const char* home = getenv("HOME");
|
||
if (home && home[0]) {
|
||
logDir = std::string(home) + "/.config/ghost";
|
||
} else {
|
||
logDir = "/tmp";
|
||
}
|
||
}
|
||
mkdir(logDir.c_str(), 0755); // 确保日志目录存在
|
||
instance.InitLogFile(logDir, instance.pid);
|
||
// daemon 模式默认打开日志,可通过 ENABLE_LOG=0 关闭
|
||
const char* envLog = getenv("ENABLE_LOG");
|
||
if (envLog) {
|
||
instance.enable = stringToBool(envLog);
|
||
} else {
|
||
instance.enable = true;
|
||
}
|
||
#endif
|
||
}
|
||
return instance;
|
||
}
|
||
|
||
// 禁止拷贝和赋值
|
||
Logger(const Logger&) = delete;
|
||
Logger& operator=(const Logger&) = delete;
|
||
|
||
// 设置日志文件名
|
||
void setLogFile(const std::string& filename)
|
||
{
|
||
std::lock_guard<std::mutex> lock(fileMutex);
|
||
logFileName = filename;
|
||
}
|
||
|
||
// 启用日志
|
||
void usingLog(bool b = true)
|
||
{
|
||
enable = b;
|
||
}
|
||
|
||
// 刷新日志队列(同步等待所有日志写入完成)
|
||
void flush()
|
||
{
|
||
// 等待队列清空
|
||
while (true) {
|
||
{
|
||
std::lock_guard<std::mutex> lock(queueMutex);
|
||
if (logQueue.empty()) break;
|
||
}
|
||
cv.notify_one();
|
||
#ifdef _WIN32
|
||
Sleep(1);
|
||
#else
|
||
usleep(1000);
|
||
#endif
|
||
}
|
||
}
|
||
|
||
// 写日志,支持 printf 格式化
|
||
void log(const char* file, int line, const char* format, ...)
|
||
{
|
||
va_list args;
|
||
va_start(args, format);
|
||
|
||
std::string message = formatString(format, args);
|
||
|
||
va_end(args);
|
||
|
||
auto timestamp = getCurrentTimestamp();
|
||
std::string id = pid.empty() ? "" : "[" + pid + "]";
|
||
|
||
std::string logEntry = file && line ?
|
||
id + "[" + timestamp + "] [" + file + ":" + std::to_string(line) + "] " + message:
|
||
id + "[" + timestamp + "] " + message;
|
||
if (enable) {
|
||
if (running) {
|
||
std::lock_guard<std::mutex> lock(queueMutex);
|
||
logQueue.push(logEntry);
|
||
} else {
|
||
writeToFile(logEntry);
|
||
}
|
||
}
|
||
#ifdef _DEBUG
|
||
#ifndef _WINDOWS
|
||
printf("%s", logEntry.c_str());
|
||
#else
|
||
OutputDebugStringA(logEntry.c_str());
|
||
#endif
|
||
#endif
|
||
cv.notify_one(); // 通知写线程
|
||
}
|
||
|
||
// 停止日志系统
|
||
void stop()
|
||
{
|
||
if (!running) return;
|
||
{
|
||
std::lock_guard<std::mutex> lock(queueMutex);
|
||
running = false; // 设置运行状态
|
||
}
|
||
cv.notify_one();
|
||
if (workerThread.joinable()) {
|
||
try {
|
||
workerThread.join();
|
||
} catch (const std::system_error& e) {
|
||
printf("Join failed: %s [%d]\n", e.what(), e.code().value());
|
||
}
|
||
}
|
||
#ifdef _WIN32
|
||
for (int i = 0; threadRun && i++ < 1000; Sleep(1));
|
||
#else
|
||
for (int i = 0; threadRun && i++ < 1000; usleep(1000));
|
||
#endif
|
||
}
|
||
|
||
private:
|
||
// 日志按月份起名
|
||
void InitLogFile(const std::string & dir, const std::string& pid)
|
||
{
|
||
time_t currentTime = time(nullptr);
|
||
tm* localTime = localtime(¤tTime);
|
||
char timeString[32];
|
||
strftime(timeString, sizeof(timeString), "%Y-%m", localTime);
|
||
char fileName[100];
|
||
#ifdef _WINDOWS
|
||
sprintf_s(fileName, "\\YAMA_%s_%s.txt", timeString, pid.c_str());
|
||
#elif defined(_WIN32)
|
||
sprintf_s(fileName, "\\log_%s_%s.txt", timeString, pid.c_str());
|
||
#else
|
||
snprintf(fileName, sizeof(fileName), "/log_%s_%s.txt", timeString, pid.c_str());
|
||
#endif
|
||
logFileName = dir + fileName;
|
||
}
|
||
std::string logFileName; // 日志文件名
|
||
bool enable; // 是否启用
|
||
bool threadRun; // 日志线程状态
|
||
std::queue<std::string> logQueue; // 日志队列
|
||
std::mutex queueMutex; // 队列互斥锁
|
||
std::condition_variable cv; // 条件变量
|
||
std::atomic<bool> running; // 是否运行
|
||
std::thread workerThread; // 后台线程
|
||
std::mutex fileMutex; // 文件写入锁
|
||
std::string pid; // 进程ID
|
||
|
||
Logger() : enable(false), threadRun(false), running(true), workerThread(&Logger::processLogs, this) {}
|
||
|
||
~Logger()
|
||
{
|
||
stop();
|
||
}
|
||
|
||
// 后台线程处理日志
|
||
// 退出语义:stop() 设 running=false 后,本线程必须把队列里**已入队**的日志
|
||
// 全部刷盘再退出。否则进程死亡前最后几条 Mprintf(包括退出原因)会丢失。
|
||
void processLogs()
|
||
{
|
||
threadRun = true;
|
||
while (true) {
|
||
std::unique_lock<std::mutex> lock(queueMutex);
|
||
cv.wait(lock, [this]() {
|
||
return !running || !logQueue.empty();
|
||
});
|
||
|
||
// drain:不带 running 判断,确保 stop() 时残留条目也写完
|
||
while (!logQueue.empty()) {
|
||
std::string logEntry = logQueue.front();
|
||
logQueue.pop();
|
||
lock.unlock();
|
||
|
||
// 写入日志文件
|
||
writeToFile(logEntry);
|
||
|
||
lock.lock();
|
||
}
|
||
|
||
// 队列已空再决定要不要退出
|
||
if (!running) break;
|
||
}
|
||
threadRun = false;
|
||
}
|
||
|
||
// 写入文件
|
||
void writeToFile(const std::string& logEntry)
|
||
{
|
||
std::lock_guard<std::mutex> lock(fileMutex);
|
||
std::ofstream logFile(logFileName, std::ios::app);
|
||
if (logFile.is_open()) {
|
||
logFile << logEntry << std::endl;
|
||
}
|
||
}
|
||
|
||
// 获取当前时间戳
|
||
std::string getCurrentTimestamp()
|
||
{
|
||
auto now = std::chrono::system_clock::now();
|
||
auto in_time_t = std::chrono::system_clock::to_time_t(now);
|
||
|
||
std::tm tm;
|
||
#ifdef _WIN32
|
||
localtime_s(&tm, &in_time_t); // Windows 安全版本
|
||
#else
|
||
localtime_r(&in_time_t, &tm); // POSIX 安全版本
|
||
#endif
|
||
|
||
std::stringstream ss;
|
||
ss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
|
||
return ss.str();
|
||
}
|
||
|
||
// 将日志级别转换为字符串
|
||
std::string logLevelToString(LogLevel level)
|
||
{
|
||
switch (level) {
|
||
case InfoLevel:
|
||
return "INFO";
|
||
case WarningLevel:
|
||
return "WARNING";
|
||
case ErrorLevel:
|
||
return "ERROR";
|
||
default:
|
||
return "UNKNOWN";
|
||
}
|
||
}
|
||
|
||
// 格式化字符串
|
||
std::string formatString(const char* format, va_list args)
|
||
{
|
||
char buffer[1024];
|
||
vsnprintf(buffer, sizeof(buffer), format, args);
|
||
return std::string(buffer);
|
||
}
|
||
};
|
||
|
||
inline const char* getFileName(const char* path)
|
||
{
|
||
const char* fileName = strrchr(path, '\\');
|
||
if (!fileName) {
|
||
fileName = strrchr(path, '/');
|
||
}
|
||
return fileName ? fileName + 1 : path;
|
||
}
|
||
|
||
#ifdef _WINDOWS
|
||
#ifdef _DEBUG
|
||
#define Mprintf(format, ...) TRACE(format, __VA_ARGS__)
|
||
#else
|
||
#define Mprintf(format, ...) Logger::getInstance().log(getFileName(__FILE__), __LINE__, format, __VA_ARGS__)
|
||
#endif
|
||
#elif defined(_WIN32)
|
||
#define Mprintf(format, ...) Logger::getInstance().log(getFileName((__FILE__)), __LINE__, (format), __VA_ARGS__)
|
||
#elif defined(__APPLE__)
|
||
// macOS: 使用 NSLog 输出到系统日志(可通过 Console.app 查看)
|
||
#ifdef Mprintf
|
||
#undef Mprintf
|
||
#endif
|
||
#ifdef __OBJC__
|
||
#define Mprintf(format, ...) NSLog(@"%@", [NSString stringWithFormat:@(format), ##__VA_ARGS__])
|
||
#else
|
||
#define Mprintf(format, ...) printf(format, ##__VA_ARGS__)
|
||
#endif
|
||
#else
|
||
// Linux: 覆盖 commands.h 中的 printf 回退定义,改用 Logger 写文件
|
||
#ifdef Mprintf
|
||
#undef Mprintf
|
||
#endif
|
||
#define Mprintf(format, ...) Logger::getInstance().log(getFileName(__FILE__), __LINE__, format, ##__VA_ARGS__)
|
||
#endif
|
||
|
||
inline void Log(const char* message)
|
||
{
|
||
return Logger::getInstance().log(NULL, 0, "%s", message);
|
||
}
|
||
|
||
inline void Logf(const char* file, int line, const char* format, ...)
|
||
{
|
||
va_list args;
|
||
va_start(args, format);
|
||
char message[1024];
|
||
vsnprintf(message, sizeof(message), format, args);
|
||
va_end(args);
|
||
return Logger::getInstance().log(getFileName(file), line, "%s", message);
|
||
}
|