GrabBag/VrUtils/Src/VrLog.cpp
2026-01-04 23:11:31 +08:00

380 lines
13 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.

#include "VrLog.h"
#include <iostream>
#include <cstring>
#include <stdarg.h>
#include <clocale>
#include <mutex>
#include <atomic>
#include <log4cpp/Category.hh>
#include <log4cpp/Appender.hh>
#include <log4cpp/FileAppender.hh>
#include <log4cpp/Priority.hh>
#include <log4cpp/PatternLayout.hh>
#include <log4cpp/RollingFileAppender.hh>
#include <log4cpp/PropertyConfigurator.hh>
#include "VrFileUtils.h"
#ifdef _WIN32
#include <windows.h>
#include <direct.h>
#include <shlobj.h>
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#pragma comment(lib,"Ws2_32.lib")
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#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<bool> isInitialized{false};
std::atomic<bool> isShutdown{false}; // 标记日志系统是否已关闭(程序退出时)
std::atomic<bool> 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(&timestamp); \
long long milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(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 <android/log.h>
#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)
{
}