#include "VrLog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "VrFileUtils.h" #ifdef _WIN32 #include #include #include #define _WINSOCK_DEPRECATED_NO_WARNINGS #include #pragma comment(lib,"Ws2_32.lib") #else #include #include #include #endif ////定义Log宏 ///******************************************************************************************************************/ #ifdef _WIN32 #define PATH_SEP "\\" #else #define PATH_SEP "/" #endif #define LOG_CONFIG_FILE "config.ini" #define LOG_PRINT_FILE "AppLog.log" // 使用函数内静态变量实现真正的全局单例(跨静态库安全) struct VrLogState { log4cpp::PatternLayout* pLayout = nullptr; log4cpp::RollingFileAppender* pRollFileAppender = nullptr; std::atomic isInitialized{false}; std::atomic isShutdown{false}; // 标记日志系统是否已关闭(程序退出时) std::atomic isTimeEnabled{true}; std::once_flag initFlag; std::once_flag uninitFlag; }; // 函数内静态变量,确保跨编译单元的唯一性 static VrLogState& GetLogState() { static VrLogState state; return state; } // 兼容旧代码的访问方式 #define m_pLayout (GetLogState().pLayout) #define m_prollfileAppender (GetLogState().pRollFileAppender) #define g_isLogInitialized (GetLogState().isInitialized) #define g_isLogShutdown (GetLogState().isShutdown) #define g_isTimeEnabled (GetLogState().isTimeEnabled) #ifdef STM32_UCOSIII #define LOG_TIME do{ \ OS_ERR err;\ OS_TICK time = OSTimeGet(&err); \ printf("[%10u]",time); \ }while(0) #else #define LOG_TIME do { \ std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); \ std::time_t timestamp = std::chrono::system_clock::to_time_t(now); \ std::tm local_time = *std::localtime(×tamp); \ long long milliseconds = std::chrono::duration_cast(now.time_since_epoch()).count(); \ printf("%d-%02d-%02d %02d:%02d:%02d.%03lld", local_time.tm_year + 1900 , local_time.tm_mon + 1, local_time.tm_mday, local_time.tm_hour, local_time.tm_min, local_time.tm_sec, milliseconds % 1000); \ } while(0) #endif #ifdef __ANDROID__ #include #define MY_LOG_VERBOSE(fmt, ...) __android_log_print(ANDROID_LOG_VERBOSE, "APPV", ##__VA_ARGS__) #define MY_LOG_DEBUG(fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, "APPD", ##__VA_ARGS__) #define MY_LOG_INFO(fmt, ...) __android_log_print(ANDROID_LOG_INFO, "APPI", ##__VA_ARGS__) #define MY_LOG_WARNING(fmt, ...) __android_log_print(ANDROID_LOG_WARN, "APPW", ##__VA_ARGS__) #define MY_LOG_ERRO(fmt, ...) __android_log_print(ANDROID_LOG_ERROR, "APPE", ##__VA_ARGS__) #else // 辅助函数:截断文件路径,最多显示15个字符(12个字符 + "...") static inline const char* TruncateFilePath(const char* filePath) { static thread_local char buffer[20]; // 线程局部存储,避免多线程问题 if (!filePath) return ""; size_t len = strlen(filePath); if (len <= 15) { // 如果长度不超过15,直接返回 return filePath; } else { // 如果超过15个字符,取前13个字符 + "..." strncpy(buffer, filePath, 13); buffer[13] = '.'; buffer[14] = '.'; buffer[15] = '\0'; return buffer; } } #define MY_LOG_VERBOSE(filePath, nLine, fmt, ...) do { if(g_isTimeEnabled) LOG_TIME; printf(" V[%15s:%4u] " fmt"", TruncateFilePath(filePath), nLine ,##__VA_ARGS__); fflush(stdout); } while(0) #define MY_LOG_DEBUG(filePath, nLine, fmt, ...) do { if(g_isTimeEnabled) LOG_TIME; printf(" D[%15s:%4u] " fmt"", TruncateFilePath(filePath), nLine ,##__VA_ARGS__); fflush(stdout); } while(0) #define MY_LOG_INFO(filePath, nLine, fmt, ...) do { if(g_isTimeEnabled) LOG_TIME; printf(" I[%15s:%4u] " fmt"", TruncateFilePath(filePath), nLine ,##__VA_ARGS__); fflush(stdout); } while(0) #define MY_LOG_WARNING(filePath, nLine, fmt, ...) do { if(g_isTimeEnabled) LOG_TIME; printf(" W[%15s:%4u] " fmt"", TruncateFilePath(filePath), nLine ,##__VA_ARGS__); fflush(stdout); } while(0) #define MY_LOG_ERRO(filePath, nLine, fmt, ...) do { if(g_isTimeEnabled) LOG_TIME; printf(" E[%15s:%4u] " fmt"", TruncateFilePath(filePath), nLine ,##__VA_ARGS__); fflush(stdout); } while(0) #endif // __ANROID__ ///******************************************************************************************************************/ // 动态获取日志路径(包含应用名称) static std::string GetLogPath() { std::string appName = "YJApp"; // 默认应用名称 // 尝试从进程路径获取应用名称 #ifdef _WIN32 // 获取用户文档目录 // 如果无法获取用户目录,则使用原来的方式 char exePath[MAX_PATH]; if (GetModuleFileNameA(NULL, exePath, MAX_PATH) != 0) { std::string fullPath(exePath); size_t pos = fullPath.find_last_of("\\"); if (pos != std::string::npos) { std::string fileName = fullPath.substr(pos + 1); size_t dotPos = fileName.find_last_of("."); if (dotPos != std::string::npos) { appName = fileName.substr(0, dotPos); } } } char userPath[MAX_PATH]; if (SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, userPath) == S_OK) { std::string userDir(userPath); return userDir + "\\" + appName + "\\Log"; } return ".\\" + appName + "\\Log"; #else // Linux/Android平台 char exePath[1024]; ssize_t len = readlink("/proc/self/exe", exePath, sizeof(exePath) - 1); if (len != -1) { exePath[len] = '\0'; std::string fullPath(exePath); size_t pos = fullPath.find_last_of("/"); if (pos != std::string::npos) { appName = fullPath.substr(pos + 1); } } #ifdef __ANDROID__ return "/sdcard/" + appName + "/Log"; #else return appName + "/Log"; #endif #endif } // 自动初始化和清理类,确保程序启动时初始化日志,退出时清理日志资源 class VrLogAutoCleaner { public: VrLogAutoCleaner() { VrLogUtils::InitLog(); } ~VrLogAutoCleaner() { VrLogUtils::UninitLog(); } }; // 静态实例,程序启动时自动初始化日志,程序退出时自动清理日志 static VrLogAutoCleaner g_logAutoCleaner; // 实际执行初始化的内部函数 static void DoInitLog() { #ifdef _WIN32 // 设置控制台代码页为 UTF-8,解决中文乱码 SetConsoleOutputCP(CP_UTF8); SetConsoleCP(CP_UTF8); // 设置 C 运行时的 locale 为 UTF-8 setlocale(LC_ALL, ".UTF-8"); #endif // if file exist then load file config std::string logPath = GetLogPath(); std::string logConfig = logPath + PATH_SEP + LOG_CONFIG_FILE; if (CVrFileUtils::IsFileExist(logConfig.c_str())) { // load profile try { log4cpp::PropertyConfigurator::configure(logConfig); } catch (log4cpp::ConfigureFailure& f) { std::cout << "Load Log Profile Error" << f.what() << std::endl; } } else { if (!CVrFileUtils::IsDirExist(logPath.c_str())) { CVrFileUtils::CreatNewDir(logPath.c_str()); } // create file appender VrLogState& state = GetLogState(); if (nullptr == state.pLayout) { state.pLayout = new log4cpp::PatternLayout; // 设置布局格式,可以控制是否自动换行 // %d{%Y-%m-%d %H:%M:%S.%l} [%p] %c - %m%n // 其中 %n 表示换行符,如果想要控制换行可以去掉或修改这个 state.pLayout->setConversionPattern("%d{%Y-%m-%d %H:%M:%S.%l} [%p] %m"); } if (nullptr == state.pRollFileAppender) { state.pRollFileAppender = new log4cpp::RollingFileAppender("AppLogAppender", logPath + PATH_SEP + LOG_PRINT_FILE, 1024 * 1024, // 单个文件大小1M 10); // 10个文件 state.pRollFileAppender->setLayout(state.pLayout); // add appender to category log4cpp::Category& root = log4cpp::Category::getRoot(); root.addAppender(state.pRollFileAppender); // set priority root.setPriority(log4cpp::Priority::DEBUG); } } g_isLogInitialized = true; } /// 初始化log void VrLogUtils::InitLog() { // 如果已经关闭,不再初始化 if (g_isLogShutdown) { return; } // 使用 call_once 确保只执行一次初始化(跨静态库安全) std::call_once(GetLogState().initFlag, DoInitLog); } // 实际执行清理的内部函数 static void DoUninitLog() { // 先设置标志,防止其他线程继续调用日志 g_isLogInitialized = false; g_isLogShutdown = true; // 标记已关闭,防止 EchoLog 重新初始化 // 注意:不调用 log4cpp::Category::shutdown() // 原因:静态对象析构顺序不确定,log4cpp 内部的静态对象可能在 // VrLogAutoCleaner 之前就已经被析构,此时调用 shutdown() 会崩溃。 // 程序退出时,操作系统会自动回收所有内存,不需要手动清理。 // 只置空指针,不删除对象(对象由 log4cpp 内部管理) VrLogState& state = GetLogState(); state.pLayout = nullptr; state.pRollFileAppender = nullptr; } /// 关闭log void VrLogUtils::UninitLog() { // 使用 call_once 确保只执行一次清理(跨静态库安全) std::call_once(GetLogState().uninitFlag, DoUninitLog); } /// 开启/关闭时间戳 void VrLogUtils::EnableTime(bool bEnable) { if (nullptr != m_pLayout) { m_pLayout->setConversionPattern(bEnable ? "%d{%Y-%m-%d %H:%M:%S.%l} [%p] %m" : "%p %m"); } g_isTimeEnabled = false; } /// 输出log void VrLogUtils::EchoLog(VrLogLevel eLogLevel, const char* sFilePath, const int nLine, const char* sLogGroup, const char* sFormat, ...) { // 如果日志系统已关闭(程序退出),直接返回,不要重新初始化 if (g_isLogShutdown) { return; } // 由于VrLogAutoCleaner会在程序启动时自动初始化,这里只做保险检查 if (!g_isLogInitialized) { InitLog(); } // load log info va_list args; va_start(args, sFormat); char szLogInfo[1024] = { 0 }; #ifdef _WIN32 vsprintf_s(szLogInfo, sFormat, args); #else vsprintf(szLogInfo, sFormat, args); #endif va_end(args); log4cpp::Category& root = log4cpp::Category::getRoot(); switch (eLogLevel) { case KELOGLEVEL_None: break; case KELOGLEVEL_Verbose: MY_LOG_VERBOSE(sFilePath, nLine, "%s", szLogInfo); if (nullptr != m_pLayout && nullptr != m_prollfileAppender) { LOG4CPP_DEBUG(root, szLogInfo); } break; case KELOGLEVEL_Debug: MY_LOG_DEBUG(sFilePath, nLine, "%s", szLogInfo); if (nullptr != m_pLayout && nullptr != m_prollfileAppender) { LOG4CPP_DEBUG(root, szLogInfo); } break; case KELOGLEVEL_Info: MY_LOG_INFO(sFilePath, nLine, "%s", szLogInfo); if (nullptr != m_pLayout && nullptr != m_prollfileAppender) { LOG4CPP_INFO(root, szLogInfo); } break; case KELOGLEVEL_Warning: MY_LOG_WARNING(sFilePath, nLine, "%s", szLogInfo); if (nullptr != m_pLayout && nullptr != m_prollfileAppender) { LOG4CPP_WARN(root, szLogInfo); } break; case KELOGLEVEL_Error: MY_LOG_ERRO(sFilePath, nLine, "%s", szLogInfo); if (nullptr != m_pLayout && nullptr != m_prollfileAppender) { LOG4CPP_ERROR(root, szLogInfo); } break; default: break; } } /// 修改log level default info void VrLogUtils::AlterLogLevel(VrLogLevel eLogLevel) { } /// 修改log输出形式 默认都输出 void VrLogUtils::AlterLogType(VrLogType eLogType) { }