思路部分参考了陈硕的 muduo,主要就是多线程写的安全性,以及一些日志分级,自动分割等常用功能,线上几十近百台机器一两年的运行没有发现什么问题
/* * encoding: gbk * created: 2013-09-05 10:33:59 * * 日志工具 * 工具在初始化的时候从堆上分配一片内存,当作二维 char 数组来用 * 每个写入线程在写入前对数组的一维下标原子加 1,作为本次写入 * 的位置,写入的位置偏移 1 个字节,第 1 个字节用作标志位,写 * 入完成后标志位置 1 * 磁盘文件落地线程循环的检查是否有未读取的日志行,并将新的 * 内存中日志写入磁盘 * * 使用方法: * #include "logger.h" * assert(Logger::Init() == 0); * LOG_INFO("sample log message %d", 123); */ #ifndef LOGGER_H #define LOGGER_H #include <errno.h> #include <string> #include <pthread.h> #include <sys/syscall.h> #include <libgen.h> #include <stdlib.h> #ifdef DEBUG #define LOG_DEBUG(format, ...) do { if (Logger::m_pBeforeLogHook==NULL || (Logger::m_pBeforeLogHook!=NULL && Logger::m_pBeforeLogHook(__FILE__, __FUNCTION__)!=0)) { Logger::m_pLog(Logger::LOG_LEVEL_DEBUG, "[%s][%d][DEBUG][%25s@%25s,%04d] " format "\n", Logger::m_szLogTimestampStr[Logger::m_iLogTimestampIndex], syscall(__NR_gettid), __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__); } } while (0) #define LOG_INFO(format, ...) do { if (Logger::m_pBeforeLogHook==NULL || (Logger::m_pBeforeLogHook!=NULL && Logger::m_pBeforeLogHook(__FILE__, __FUNCTION__)!=0)) { Logger::m_pLog(Logger::LOG_LEVEL_INFO, "[%s][%d] [INFO][%25s@%25s,%04d] " format "\n", Logger::m_szLogTimestampStr[Logger::m_iLogTimestampIndex], syscall(__NR_gettid), __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__); } } while (0) #define LOG_WARN(format, ...) do { if (Logger::m_pBeforeLogHook==NULL || (Logger::m_pBeforeLogHook!=NULL && Logger::m_pBeforeLogHook(__FILE__, __FUNCTION__)!=0)) { Logger::m_pLog(Logger::LOG_LEVEL_WARN, "\033[1;33m" "[%s][%d] [WARN][%25s@%25s,%04d] " format "\033[0m" "\n", Logger::m_szLogTimestampStr[Logger::m_iLogTimestampIndex], syscall(__NR_gettid), __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__); } } while (0) #define LOG_ERR(format, ...) do { if (Logger::m_pBeforeLogHook==NULL || (Logger::m_pBeforeLogHook!=NULL && Logger::m_pBeforeLogHook(__FILE__, __FUNCTION__)!=0)) { Logger::m_pLog(Logger::LOG_LEVEL_ERROR, "\033[1;31m" "[%s][%d][ERROR][%25s@%25s,%04d] " format "\033[0m" "\n", Logger::m_szLogTimestampStr[Logger::m_iLogTimestampIndex], syscall(__NR_gettid), __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__); } } while (0) #else #define LOG_DEBUG(format, ...) Logger::m_pLog(Logger::LOG_LEVEL_DEBUG, "[%s][DEBUG][%25s@%25s,%04d] " format "\n", Logger::m_szLogTimestampStr[Logger::m_iLogTimestampIndex], __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__) #define LOG_INFO(format, ...) Logger::m_pLog(Logger::LOG_LEVEL_INFO, "[%s] [INFO][%25s@%25s,%04d] " format "\n", Logger::m_szLogTimestampStr[Logger::m_iLogTimestampIndex], __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__) #define LOG_WARN(format, ...) Logger::m_pLog(Logger::LOG_LEVEL_WARN, "[%s] [WARN][%25s@%25s,%04d] " format "\n", Logger::m_szLogTimestampStr[Logger::m_iLogTimestampIndex], __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__) #define LOG_ERR(format, ...) Logger::m_pLog(Logger::LOG_LEVEL_ERROR, "[%s][ERROR][%25s@%25s,%04d] " format "\n", Logger::m_szLogTimestampStr[Logger::m_iLogTimestampIndex], __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__) #endif class Logger { public: enum eLogLevel {LOG_LEVEL_DEBUG = 10, LOG_LEVEL_INFO = 20, LOG_LEVEL_WARN = 30, LOG_LEVEL_ERROR = 40, LOG_LEVEL_OFF = 50}; //************************************ // Method: Init // FullName: Logger::Init // Access: public static // Returns: int 0 表示初始化成功,-1 表示失败 // Parameter: logLevel level 日志过滤等级,默认 INFO,低于该指定等级的日志将不会写入磁盘文件,也不会写入内存 // Parameter: const char * pLogFileDir 日志文件存放的目录,默认当前目录 // Parameter: const char * pLogFileBaseName 日志文件的文件名,不需要包括扩展名,默认当前进程名,当文件名置为 stdout 时,即时输出到屏幕 // Parameter: uint32_t uiLogFileRollingSize 日志文件的滚动条件,文件尺寸(字节),默认 10 MB // Parameter: uint32_t uiMaxCharPerLine 每行最大字符数 // Parameter: uint32_t uiLogBufferLine 缓冲区行数 //************************************ static int Init(eLogLevel level = LOG_LEVEL_INFO, const char* pLogFileDir = "./", const char* pLogFileBaseName = basename(getenv("_")), uint32_t uiLogFileRollingSize = 10*1024*1024, uint32_t uiMaxCharPerLine = 16384, uint32_t uiLogBufferLine = 4096); static void (*m_pLog)(eLogLevel level, const char* format, ...); static void LogToMem(eLogLevel level, const char* format, ...); static void LogToStdout(eLogLevel level, const char* format, ...); static int (*m_pBeforeLogHook)(const char* szFilename, const char* szFunctionName); static char m_szLogTimestampStr[2][32]; static time_t m_stLogTimestamp; static int m_iLogTimestampIndex; // 每行最大字符数,每行的第一个字节用作标志位,为 1 表示该行写入完成可供读取,读前检查是否为 1 读取后置为 0 static uint32_t m_uiMaxCharPerLine; // 最大行数,必须保证 m_uiLogBufferLines 是 2 的整数次幂 static uint32_t m_uiLogBufferLines; private: Logger(); // 不允许实例化 static pthread_mutex_t m_stInitLock; // 防止多个线程同时调用 Init 函数 static bool m_bInited; // 标识日志库是否初始化完毕 static eLogLevel m_eLevel; // 标识日志库的记录等级,大于等于这个等级的,才会写入内存,然后被写入文件 static char *m_pLogBufferMem; // 指向日志内存的首字节 static uint32_t m_uiWriteLineCount; // 日志内存写入行数累加器 }; // 磁盘文件写入线程 void* LogDumper(void *args); struct LogDumperArgs { char* pLogBufferMem; std::string strLogFileDir; std::string strLogFileName; uint32_t uiLogFileMaxSize; }; // 日志时间字符串更新线程,每 1 秒更新一次时间字符串,交替更新两个字符串数组 void* LogTimeStrGenerator(void*); #endif
#include <string.h> #include <string> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <libgen.h> #include <unistd.h> #include <stdint.h> #include <pthread.h> #include <time.h> #include <stdarg.h> #include <ifaddrs.h> #include <net/if.h> #include <arpa/inet.h> #include "logger.h" int Logger::Init(eLogLevel level, const char* pLogFileDir, const char* pLogFileBaseName, uint32_t uiLogFileRollingSize, uint32_t uiMaxCharPerLine, uint32_t uiLogBufferLines) { pthread_mutex_lock(&m_stInitLock); if (m_bInited || level == LOG_LEVEL_OFF) { pthread_mutex_unlock(&m_stInitLock); return 0; } m_eLevel = level; m_uiMaxCharPerLine = uiMaxCharPerLine; if (m_uiMaxCharPerLine <= 32) { fprintf(stderr, "max char per line config error, should greater than 32, now using default value 16384\n"); m_uiMaxCharPerLine = 16384; } m_uiLogBufferLines = uiLogBufferLines; if (m_uiLogBufferLines & (m_uiLogBufferLines-1) != 0) { fprintf(stderr, "log buffer line config error, must be power of 2, like 2048 4096, now using default value 4096\n"); m_uiLogBufferLines = 4096; } memset(m_szLogTimestampStr, 0, sizeof(m_szLogTimestampStr)); time_t stRawtime = time(NULL); struct tm* stTimeinfo = localtime(&stRawtime); strftime(Logger::m_szLogTimestampStr[0], sizeof(Logger::m_szLogTimestampStr[0])-1, "%Y-%m-%d %H:%M:%S", stTimeinfo); m_iLogTimestampIndex = 0; pthread_t tid; if (pthread_create(&tid, NULL, LogTimeStrGenerator, NULL) != 0) { fprintf(stderr, "create log timestamp thread failed: %s\n", strerror(errno)); pthread_mutex_unlock(&m_stInitLock); return -1; } if (strcmp("stdout", pLogFileBaseName) == 0) { m_pLog = &(Logger::LogToStdout); m_bInited = true; pthread_mutex_unlock(&m_stInitLock); return 0; } else { m_pLog = &(Logger::LogToMem); } int iLogBufferMemSize = m_uiMaxCharPerLine * m_uiLogBufferLines; m_pLogBufferMem = (char *)malloc(iLogBufferMemSize); if (m_pLogBufferMem == NULL) { fprintf(stderr, "alloc heap memory failed: %s\n", strerror(errno)); pthread_mutex_unlock(&m_stInitLock); return -1; } m_uiWriteLineCount = 0; memset(m_pLogBufferMem, 0, iLogBufferMemSize); struct LogDumperArgs* pLogDumperArgs = new struct LogDumperArgs; pLogDumperArgs->pLogBufferMem = m_pLogBufferMem; pLogDumperArgs->strLogFileDir.assign(pLogFileDir); pLogDumperArgs->strLogFileName.assign(pLogFileBaseName); pLogDumperArgs->uiLogFileMaxSize = uiLogFileRollingSize; if (pthread_create(&tid, NULL, LogDumper, (void *)pLogDumperArgs) != 0) { fprintf(stderr, "create log dump thread failed: %s\n", strerror(errno)); pthread_mutex_unlock(&m_stInitLock); return -1; } m_bInited = true; pthread_mutex_unlock(&m_stInitLock); return 0; } void Logger::LogToMem(eLogLevel level, const char* format, ...) { if (m_bInited && level >= m_eLevel) { va_list args; va_start(args, format); char *pWritePtr = m_pLogBufferMem+((__sync_add_and_fetch(&m_uiWriteLineCount, 1) % m_uiLogBufferLines) * m_uiMaxCharPerLine); vsnprintf(pWritePtr+1, m_uiMaxCharPerLine-2, format, args); *(pWritePtr) = 1; va_end(args); } } void Logger::LogToStdout(eLogLevel level, const char* format, ...) { if (m_bInited && level >= m_eLevel) { va_list args; va_start(args, format); vprintf(format, args); va_end(args); } } pthread_mutex_t Logger::m_stInitLock = PTHREAD_MUTEX_INITIALIZER; bool Logger::m_bInited = false; Logger::eLogLevel Logger::m_eLevel = Logger::LOG_LEVEL_INFO; char* Logger::m_pLogBufferMem = NULL; uint32_t Logger::m_uiWriteLineCount = 0; int Logger::m_iLogTimestampIndex = 0; char Logger::m_szLogTimestampStr[2][32] = {{0}, {0}}; time_t Logger::m_stLogTimestamp = 0; uint32_t Logger::m_uiMaxCharPerLine = 16384; uint32_t Logger::m_uiLogBufferLines = 4096; void (*Logger::m_pLog)(Logger::eLogLevel level, const char* format, ...) = &(Logger::LogToMem); int (*Logger::m_pBeforeLogHook)(const char*, const char*) = NULL; void* LogTimeStrGenerator(void*) { while (1) { Logger::m_stLogTimestamp = time(NULL); struct tm* stTimeinfo = localtime(&Logger::m_stLogTimestamp); strftime(Logger::m_szLogTimestampStr[(Logger::m_iLogTimestampIndex+1)%2], sizeof(Logger::m_szLogTimestampStr[0])-1, "%Y-%m-%d %H:%M:%S", stTimeinfo); Logger::m_iLogTimestampIndex = (Logger::m_iLogTimestampIndex + 1) % 2; sleep(1); } return NULL; } void* LogDumper(void *args) { struct LogDumperArgs* pLogDumperArgs = (struct LogDumperArgs*)args; char* pLogBufferMem = pLogDumperArgs->pLogBufferMem; const std::string strLogFileDir = pLogDumperArgs->strLogFileDir; const std::string strLogFileName = pLogDumperArgs->strLogFileName; const uint32_t uiLogFileMaxSize = pLogDumperArgs->uiLogFileMaxSize; delete pLogDumperArgs; pLogDumperArgs = NULL; args = NULL; uint32_t iReadLineCount = 1; // 获取 IP 地址,用于文件名 char* pSzIpAddr = "0.0.0.0"; char szIpAddrBuffer[32] = {0}; struct ifaddrs* pIfAddrs; if (getifaddrs(&pIfAddrs) == 0) { for (struct ifaddrs* pIfAddr = pIfAddrs; pIfAddr; pIfAddr = pIfAddr->ifa_next) { if (pIfAddr->ifa_addr == NULL || !(pIfAddr->ifa_flags & IFF_UP) || strncmp(pIfAddr->ifa_name, "eth1", strlen("eth1")) != 0) { continue; } if (inet_ntop(pIfAddr->ifa_addr->sa_family, &((struct sockaddr_in *)pIfAddr->ifa_addr)->sin_addr, szIpAddrBuffer, sizeof(szIpAddrBuffer))) { pSzIpAddr = szIpAddrBuffer; break; } } freeifaddrs(pIfAddrs); } // 获取进程 ID,用于文件名 pid_t pid = getpid(); // 检查目录是否存在,不存在则自动创建 char szMkdirpCmd[512] = {0}; snprintf(szMkdirpCmd, sizeof(szMkdirpCmd), "mkdir -p %s", strLogFileDir.c_str()); system(szMkdirpCmd); // 开始写入文件 while (1) { // 获取当前时间,用于文件名 time_t stRawtime = time(NULL); struct tm* stTimeinfo = localtime(&stRawtime); char pTimeBuffer[80] = {0}, pLogFileFullName[512] = {0}, pLogFileLinkName[512] = {0}; strftime(pTimeBuffer, sizeof(pTimeBuffer)-1, "%Y%m%d%H%M%S", stTimeinfo); snprintf(pLogFileFullName, sizeof(pLogFileFullName)-1, "%s/%s_%s_%s_%d.log", strLogFileDir.c_str(), strLogFileName.c_str(), pTimeBuffer, pSzIpAddr, pid); snprintf(pLogFileLinkName, sizeof(pLogFileLinkName)-1, "%s.log", strLogFileName.c_str()); FILE* fp = fopen(pLogFileFullName, "a+"); if (fp == NULL) { fprintf(stderr, "open log file %s failed: %s\n", pLogFileFullName, strerror(errno)); return NULL; } // 建立软链接 unlink(pLogFileLinkName); symlink(pLogFileFullName, pLogFileLinkName); uint32_t uiWriteByteCount = 0; time_t stLastFlushTimestamp = 0; while (1) { char* pReadPtr = pLogBufferMem+((iReadLineCount % Logger::m_uiLogBufferLines) * Logger::m_uiMaxCharPerLine); if (*pReadPtr == 1) { uiWriteByteCount += fprintf(fp, "%s", pReadPtr + 1); *pReadPtr = 0; iReadLineCount++; if (uiWriteByteCount > uiLogFileMaxSize) { break; } } else { if (Logger::m_stLogTimestamp - stLastFlushTimestamp >= 3) { stLastFlushTimestamp = Logger::m_stLogTimestamp; fflush(fp); } usleep(5000); } } fclose(fp); } return NULL; }
Pingback: 动态链接库中的全局变量 | ZRJ