/** @file log.c @brief 系统日志接口文件 @version 1.0.0 */ #include #include #include #include #include #include #include #include #include #include #include "uthash/utlist.h" #include "uthash/utstring.h" #include "common.h" #include "log.h" #define SHOW_CONSOLE_RED ("\033[31;48m\033[1m%s\033[0m%c") #define SHOW_CONSOLE_YELLOW ("\033[33;48m\033[1m%s\033[0m%c") #define SHOW_CONSOLE_GREEN ("\033[32;48m\033[1m%s\033[0m%c") #define SHOW_CONSOLE_BLUE ("\033[34;48m\033[1m%s\033[0m%c") #define LOG_FILE_BASEDIR (".") #define MAX_LOG_FILE_SIZE (1024 * 1024) #define MAX_PATH (256) typedef struct LOG_ITEM { unsigned int level; int isPrinted; int isAddTags; struct timeval timestamp; char *pLogContent; struct LOG_ITEM *next, *prev; } *PLOG_ITEM; typedef struct { pid_t pid; char exeName[MAX_PATH]; char logFilePath[MAX_PATH]; FILE *pLogFile; } LOG_PROCESS_INFO, *PLOG_PROCESS_INFO; static int g_bEnableLog = TRUE; // 是否启用 Log 功能 static int g_bEnLogToFile = TRUE; static char g_strLogTag[32]; // Log 标志 static unsigned int g_LogRdPos = 0; static pthread_t g_logThreadId; static LOG_PROCESS_INFO g_LogProcessInfo; static pthread_mutex_t g_logLock; static PLOG_ITEM g_pLogItemList = NULL; static unsigned int g_iMinLevel = LOG_Fatal | LOG_Error | LOG_Warn | LOG_Debug | LOG_Info | LOG_Step; /** * @brief Log 调试等级转字符串 * @param level 调试等级 * @return 调试等级对应的字符串 */ const char *LogLevelToStr(unsigned int level) { switch(level) { case LOG_Test: return "T"; case LOG_Info: return "I"; case LOG_Call: return "C"; case LOG_Debug: return "D"; case LOG_Warn: return "W"; case LOG_Error: return "E"; case LOG_Fatal: return "F"; case LOG_Step: return "S"; case LOG_Devp: return "V"; case LOG_Unknown: return "U"; case LOG_All: return "A"; default: return "?"; } } static void __logColorOutput(const char *pColFmt, UT_string *pLog) { if(pLog == NULL) { return; } if(pColFmt == NULL) { print("%s", utstring_body(pLog)); } else { if(utstring_find(pLog, -1, "\n", 1) == utstring_len(pLog) - 1) { char *pLogArray = utstring_body(pLog); pLogArray[utstring_len(pLog) - 1] = 0; print(pColFmt, pLogArray, '\n'); strcat(pLogArray, "\n"); } else { print(pColFmt, utstring_body(pLog), '\0'); } } } static void *__logOutputThread(void *p) { UNUSED(p); while(g_bEnableLog) { int isWriteLog = FALSE; PLOG_ITEM pItem = NULL, pTmp = NULL; pthread_mutex_lock(&g_logLock); LL_FOREACH_SAFE(g_pLogItemList, pItem, pTmp) { UT_string *pLogStr; struct tm lTime; int logFileSize = 0; if(++g_LogRdPos % 100 == 0) { GET_FILE_SIZE(g_LogProcessInfo.logFilePath, logFileSize); } localtime_r(&(pItem->timestamp.tv_sec), &lTime); utstring_new(pLogStr); if(pItem->isAddTags) { utstring_printf(pLogStr, "[%04d-%02d-%02d %02d:%02d:%02d.%03ld] [%s] %s", lTime.tm_year + 1900, lTime.tm_mon + 1, lTime.tm_mday, lTime.tm_hour, lTime.tm_min, lTime.tm_sec, pItem->timestamp.tv_usec / 1000, LogLevelToStr(pItem->level), pItem->pLogContent); } else { utstring_printf(pLogStr, "%s", pItem->pLogContent); } if(pItem->isPrinted == FALSE) { if(pItem->level & LOG_Error || pItem->level & LOG_Fatal) { __logColorOutput(SHOW_CONSOLE_RED, pLogStr); } else if(pItem->level & LOG_Warn || pItem->level & LOG_Unknown) { __logColorOutput(SHOW_CONSOLE_YELLOW, pLogStr); } else if(pItem->level & LOG_Test || pItem->level & LOG_Call) { __logColorOutput(SHOW_CONSOLE_BLUE, pLogStr); } else if(pItem->level & LOG_Devp) { __logColorOutput(SHOW_CONSOLE_GREEN, pLogStr); } else { print("%s", utstring_body(pLogStr)); } pItem->isPrinted = TRUE; } if(g_LogProcessInfo.pLogFile != NULL && g_bEnLogToFile) { if(logFileSize >= MAX_LOG_FILE_SIZE) { fflush(g_LogProcessInfo.pLogFile); fclose(g_LogProcessInfo.pLogFile); g_LogProcessInfo.pLogFile = fopen(g_LogProcessInfo.logFilePath, "w+"); } if(g_LogProcessInfo.pLogFile) { fwrite(utstring_body(pLogStr), 1, utstring_len(pLogStr), g_LogProcessInfo.pLogFile); } isWriteLog = TRUE; } LL_DELETE(g_pLogItemList, pItem); utstring_free(pLogStr); free(pItem->pLogContent); free(pItem); if(g_LogRdPos % 100 == 0) { break; } } pthread_mutex_unlock(&g_logLock); usleep(1000); if(g_LogProcessInfo.pLogFile != NULL && isWriteLog) { fflush(g_LogProcessInfo.pLogFile); } } pthread_detach(pthread_self()); g_bEnableLog = TRUE; return NULL; } /** * @brief 设置调试等级 * @param level 调试等级 * @param iEnable 1 打开调试等级, 0 关闭调试等级 */ void IHW_EnableLogLevel(unsigned int level, int iEnable) { if(iEnable > 0) { g_iMinLevel |= level; } else { g_iMinLevel &= ~(level); } } static void __runLogService(void) { pthread_create(&g_logThreadId, NULL, __logOutputThread, NULL); } /** * @brief 初始化系统日志功能 * @param pLogTag 系统日志标志 * @param pPath 系统日志保存路径 * @param bEnable 打开/关闭调试信息 */ void IHW_InitLOG(const char *pLogTag, const char *pPath, int bEnable) { char strPath[MAX_PATH * 2]; (void)pPath; pthread_mutex_init(&g_logLock, 0); g_LogRdPos = 0; memset(g_strLogTag, 0, 32); memset(&g_LogProcessInfo, 0, sizeof(LOG_PROCESS_INFO)); if(pLogTag == NULL) { strcpy(g_strLogTag, ""); } else { strncpy(g_strLogTag, pLogTag, 31); } memset(strPath, 0, MAX_PATH * 2); g_LogProcessInfo.pid = getpid(); if(readlink("/proc/self/exe", strPath, MAX_PATH) == -1) { strcpy(g_LogProcessInfo.exeName, pLogTag); } else { char *pExeName = strrchr(strPath, '/'); if(pExeName == NULL) { strncpy(g_LogProcessInfo.exeName, strPath, MAX_PATH - 1); } else { strncpy(g_LogProcessInfo.exeName, pExeName + 1, MAX_PATH - 1); } } memset(g_LogProcessInfo.logFilePath, 0, MAX_PATH); sprintf(g_LogProcessInfo.logFilePath, "%s/%s_%d.log", LOG_FILE_BASEDIR, g_LogProcessInfo.exeName, g_LogProcessInfo.pid); memset(strPath, 0, MAX_PATH); //sprintf(strPath, "rm -f %s/%s_*.log > /dev/zero", LOG_FILE_BASEDIR, g_LogProcessInfo.exeName); //ret = system(strPath); //g_LogProcessInfo.pLogFile = fopen(g_LogProcessInfo.logFilePath, "w+"); g_bEnableLog = bEnable; __runLogService(); } static void __logTo(unsigned int level, int isAddTag, char *pMsg, int isPrint) { PLOG_ITEM pLogItem; pLogItem = (PLOG_ITEM)malloc(sizeof(struct LOG_ITEM)); if(pLogItem == NULL) { return; } pLogItem->pLogContent = strdup(pMsg); pLogItem->isPrinted = isPrint ? FALSE : TRUE; pLogItem->level = level; pLogItem->isAddTags = isAddTag ? TRUE : FALSE; gettimeofday(&(pLogItem->timestamp), NULL); pthread_mutex_lock(&g_logLock); LL_APPEND(g_pLogItemList, pLogItem); pthread_mutex_unlock(&g_logLock); } void IHW_LogStrWithoutPrint(unsigned int level, char *pMsg) { __logTo(level, TRUE, pMsg, FALSE); } void IHW_LogRawString(unsigned int level, char *pMsg) { __logTo(level, TRUE, pMsg, TRUE); } /** * @brief 输出调试信息 * @param cFlag 调试信息开关 * @param pMsg 调试信息内容 */ void IHW_LOG_UNTAG(unsigned int level, const char *pMsg, ...) { va_list arg_ptr; UT_string *pLogContent; if(!g_bEnableLog) { return; } // 检查调试等级 if(!(g_iMinLevel & level)) { return; } utstring_new(pLogContent); va_start(arg_ptr, pMsg); utstring_printf_va(pLogContent, pMsg, arg_ptr); va_end(arg_ptr); __logTo(level, FALSE, utstring_body(pLogContent), TRUE); utstring_free(pLogContent); } /** * @brief 输出调试信息 * @param cFlag 调试信息开关 * @param pMsg 调试信息内容 */ void IHW_LOG(unsigned int level, const char *pMsg, ...) { UT_string *pLogContent = NULL; va_list arg_ptr; if(!g_bEnableLog) { fprintf(stdout, "skip2\n"); return; } // 检查调试等级 if(!(g_iMinLevel & level)) { fprintf(stdout, "skip1\n"); return; } utstring_new(pLogContent); va_start(arg_ptr, pMsg); utstring_printf_va(pLogContent, pMsg, arg_ptr); va_end(arg_ptr); __logTo(level, TRUE, utstring_body(pLogContent), TRUE); utstring_free(pLogContent); } /** * @brief 等待全部日志输出 */ void IHW_WaitFinishLogout(void) { PLOG_ITEM pItem = NULL; int count; do { LL_COUNT(g_pLogItemList, pItem, count); usleep(1000); } while(count != 0); g_bEnableLog = FALSE; do { LL_COUNT(g_pLogItemList, pItem, count); usleep(1000); } while(!g_bEnableLog); } /** * @brief 获取日志等级字符串 * @param lv 日志等级 * @return 当前日志等级的字符串值 */ const char *LogLeveToString(unsigned int lv) { switch(lv) { case LOG_Fatal: return "LOG_Fatal"; case LOG_Error: return "LOG_Error"; case LOG_Warn: return "LOG_Warn"; case LOG_Debug: return "LOG_Debug"; case LOG_Info: return "LOG_Info"; case LOG_Test: return "LOG_Test"; case LOG_Call: return "LOG_Call"; case LOG_Devp: return "LOG_Devp"; case LOG_Step: return "LOG_Step"; case LOG_Unknown: return "LOG_Unknown"; case LOG_All: return "LOG_All"; case LOG_Close: return "LOG_Close"; default: return "Unknown"; } }