Files
SimpleRemoter/common/logger.h

361 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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(&currentTime);
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);
}