思路部分参考了陈硕的 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