1、c++异步日志
0、思维导图
1、头文件
/**
* @desc: 异步日志类,AsyncLog.h
* @author: zhangyl
* @date: 2019.04.13
*/
#ifndef __ASYNC_LOG_H__
#define __ASYNC_LOG_H__
#include <stdio.h>
#include <string>
#include <list>
#include <thread>
#include <memory>
#include <mutex>
#include <condition_variable>
//#ifdef LOG_EXPORTS
//#define LOG_API __declspec(dllexport)
//#else
//#define LOG_API __declspec(dllimport)
//#endif
#define LOG_API
enum LOG_LEVEL
{
LOG_LEVEL_TRACE,
LOG_LEVEL_DEBUG,
LOG_LEVEL_INFO,
LOG_LEVEL_WARNING,
LOG_LEVEL_ERROR, //用于业务错误
LOG_LEVEL_SYSERROR, //用于技术框架本身的错误
LOG_LEVEL_FATAL, //FATAL 级别的日志会让在程序输出日志后退出
LOG_LEVEL_CRITICAL //CRITICAL 日志不受日志级别控制,总是输出
};
//TODO: 多增加几个策略
//注意:如果打印的日志信息中有中文,则格式化字符串要用_T()宏包裹起来,
//e.g. LOGI(_T("GroupID=%u, GroupName=%s, GroupName=%s."), lpGroupInfo->m_nGroupCode, lpGroupInfo->m_strAccount.c_str(), lpGroupInfo->m_strName.c_str());
#define LOGT(...) CAsyncLog::output(LOG_LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__)
#define LOGD(...) CAsyncLog::output(LOG_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define LOGI(...) CAsyncLog::output(LOG_LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__)
#define LOGW(...) CAsyncLog::output(LOG_LEVEL_WARNING, __FILE__, __LINE__,__VA_ARGS__)
#define LOGE(...) CAsyncLog::output(LOG_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#define LOGSYSE(...) CAsyncLog::output(LOG_LEVEL_SYSERROR, __FILE__, __LINE__, __VA_ARGS__)
#define LOGF(...) CAsyncLog::output(LOG_LEVEL_FATAL, __FILE__, __LINE__, __VA_ARGS__) //为了让FATAL级别的日志能立即crash程序,采取同步写日志的方法
#define LOGC(...) CAsyncLog::output(LOG_LEVEL_CRITICAL, __FILE__, __LINE__, __VA_ARGS__) //关键信息,无视日志级别,总是输出
//用于输出数据包的二进制格式
#define LOG_DEBUG_BIN(buf, buflength) CAsyncLog::outputBinary(buf, buflength)
class LOG_API CAsyncLog
{
public:
static bool init(const char* pszLogFileName = nullptr, bool bTruncateLongLine = false, int64_t nRollSize = 10 * 1024 * 1024);
static void uninit();
static void setLevel(LOG_LEVEL nLevel);
static bool isRunning();
//不输出线程ID号和所在函数签名、行号
static bool output(long nLevel, const char* pszFmt, ...);
//输出线程ID号和所在函数签名、行号
static bool output(long nLevel, const char* pszFileName, int nLineNo, const char* pszFmt, ...);
static bool outputBinary(unsigned char* buffer, size_t size);
private:
CAsyncLog() = delete;
~CAsyncLog() = delete;
CAsyncLog(const CAsyncLog& rhs) = delete;
CAsyncLog& operator=(const CAsyncLog& rhs) = delete;
static void makeLinePrefix(long nLevel, std::string& strPrefix);
static void getTime(char* pszTime, int nTimeStrLength);
static bool createNewFile(const char* pszLogFileName);
static bool writeToFile(const std::string& data);
//让程序主动崩溃
static void crash();
static const char* ullto4Str(int n);
static char* formLog(int& index, char* szbuf, size_t size_buf, unsigned char* buffer, size_t size);
static void writeThreadProc();
private:
static bool m_bToFile; //日志写入文件还是写到控制台
static FILE* m_hLogFile;
static std::string m_strFileName; //日志文件名
static std::string m_strFileNamePID; //文件名中的进程id
static bool m_bTruncateLongLog; //长日志是否截断
static LOG_LEVEL m_nCurrentLevel; //当前日志级别
static int64_t m_nFileRollSize; //单个日志文件的最大字节数
static int64_t m_nCurrentWrittenSize; //已经写入的字节数目
static std::list<std::string> m_listLinesToWrite; //待写入的日志
static std::unique_ptr<std::thread> m_spWriteThread;
static std::mutex m_mutexWrite;
static std::condition_variable m_cvWrite;
static bool m_bExit; //退出标志
static bool m_bRunning; //运行标志
};
#endif // !__ASYNC_LOG_H__
2、cpp文件
/**
* @desc: 异步日志类,AsyncLog.cpp
* @author: zhangyl
* @date: 2019.04.13
*/
#include "AsyncLog.h"
#include <ctime>
#include <time.h>
#include <sys/timeb.h>
#include <stdio.h>
#include <string.h>
#include <sstream>
#include <iostream>
#include <stdarg.h>
#include "../base/Platform.h"
#define MAX_LINE_LENGTH 256
#define DEFAULT_ROLL_SIZE 10 * 1024 * 1024
bool CAsyncLog::m_bTruncateLongLog = false;
FILE* CAsyncLog::m_hLogFile = NULL;
std::string CAsyncLog::m_strFileName = "default";
std::string CAsyncLog::m_strFileNamePID = "";
LOG_LEVEL CAsyncLog::m_nCurrentLevel = LOG_LEVEL_INFO;
int64_t CAsyncLog::m_nFileRollSize = DEFAULT_ROLL_SIZE;
int64_t CAsyncLog::m_nCurrentWrittenSize = 0;
std::list<std::string> CAsyncLog::m_listLinesToWrite;
std::unique_ptr<std::thread> CAsyncLog::m_spWriteThread;
std::mutex CAsyncLog::m_mutexWrite;
std::condition_variable CAsyncLog::m_cvWrite;
bool CAsyncLog::CAsyncLog::m_bExit = false;
bool CAsyncLog::m_bRunning = false;
bool CAsyncLog::init(const char* pszLogFileName/* = nullptr*/, bool bTruncateLongLine/* = false*/, int64_t nRollSize/* = 10 * 1024 * 1024*/)
{
m_bTruncateLongLog = bTruncateLongLine;
m_nFileRollSize = nRollSize;
if (pszLogFileName == nullptr || pszLogFileName[0] == 0)
{
m_strFileName.clear();
}
else
m_strFileName = pszLogFileName;
//获取进程id,这样快速看到同一个进程的不同日志文件
char szPID[8];
#ifdef WIN32
snprintf(szPID, sizeof(szPID), "%05d", (int)::GetCurrentProcessId());
#else
snprintf(szPID, sizeof(szPID), "%05d", (int)::getpid());
#endif
m_strFileNamePID = szPID;
//TODO:创建文件夹
m_spWriteThread.reset(new std::thread(writeThreadProc));
return true;
}
void CAsyncLog::uninit()
{
m_bExit = true;
m_cvWrite.notify_one();
if (m_spWriteThread->joinable())
m_spWriteThread->join();
if(m_hLogFile != nullptr)
{
fclose(m_hLogFile);
m_hLogFile = nullptr;
}
}
void CAsyncLog::setLevel(LOG_LEVEL nLevel)
{
if (nLevel < LOG_LEVEL_TRACE || nLevel > LOG_LEVEL_FATAL)
return;
m_nCurrentLevel = nLevel;
}
bool CAsyncLog::isRunning()
{
return m_bRunning;
}
bool CAsyncLog::output(long nLevel, const char* pszFmt, ...)
{
if (nLevel != LOG_LEVEL_CRITICAL)
{
if (nLevel < m_nCurrentLevel)
return false;
}
std::string strLine;
makeLinePrefix(nLevel, strLine);
//log正文
std::string strLogMsg;
//先计算一下不定参数的长度,以便于分配空间
va_list ap;
va_start(ap, pszFmt);
int nLogMsgLength = vsnprintf(NULL, 0, pszFmt, ap);
va_end(ap);
//容量必须算上最后一个\0
if ((int)strLogMsg.capacity() < nLogMsgLength + 1)
{
strLogMsg.resize(nLogMsgLength + 1);
}
va_list aq;
va_start(aq, pszFmt);
vsnprintf((char*)strLogMsg.data(), strLogMsg.capacity(), pszFmt, aq);
va_end(aq);
//string内容正确但length不对,恢复一下其length
std::string strMsgFormal;
strMsgFormal.append(strLogMsg.c_str(), nLogMsgLength);
//如果日志开启截断,长日志只取前MAX_LINE_LENGTH个字符
if (m_bTruncateLongLog)
strMsgFormal = strMsgFormal.substr(0, MAX_LINE_LENGTH);
strLine += strMsgFormal;
//不是输出到控制台才会在每一行末尾加一个换行符
if (!m_strFileName.empty())
{
strLine += "\n";
}
if (nLevel != LOG_LEVEL_FATAL)
{
std::lock_guard<std::mutex> lock_guard(m_mutexWrite);
m_listLinesToWrite.push_back(strLine);
m_cvWrite.notify_one();
}
else
{
//为了让FATAL级别的日志能立即crash程序,采取同步写日志的方法
std::cout << strLine << std::endl;
#ifdef _WIN32
OutputDebugStringA(strLine.c_str());
OutputDebugStringA("\n");
#endif
if (!m_strFileName.empty())
{
if (m_hLogFile == nullptr)
{
//新建文件
char szNow[64];
time_t now = time(NULL);
tm time;
#ifdef _WIN32
localtime_s(&time, &now);
#else
localtime_r(&now, &time);
#endif
strftime(szNow, sizeof(szNow), "%Y%m%d%H%M%S", &time);
std::string strNewFileName(m_strFileName);
strNewFileName += ".";
strNewFileName += szNow;
strNewFileName += ".";
strNewFileName += m_strFileNamePID;
strNewFileName += ".log";
if (!createNewFile(strNewFileName.c_str()))
return false;
}// end inner if
writeToFile(strLine);
}// end outer-if
//让程序主动crash掉
crash();
}
return true;
}
bool CAsyncLog::output(long nLevel, const char* pszFileName, int nLineNo, const char* pszFmt, ...)
{
if (nLevel != LOG_LEVEL_CRITICAL)
{
if (nLevel < m_nCurrentLevel)
return false;
}
std::string strLine;
makeLinePrefix(nLevel, strLine);
//函数签名
char szFileName[512] = { 0 };
snprintf(szFileName, sizeof(szFileName), "[%s:%d]", pszFileName, nLineNo);
strLine += szFileName;
//日志正文
std::string strLogMsg;
//先计算一下不定参数的长度,以便于分配空间
va_list ap;
va_start(ap, pszFmt);
int nLogMsgLength = vsnprintf(NULL, 0, pszFmt, ap);
va_end(ap);
//容量必须算上最后一个\0
if ((int)strLogMsg.capacity() < nLogMsgLength + 1)
{
strLogMsg.resize(nLogMsgLength + 1);
}
va_list aq;
va_start(aq, pszFmt);
vsnprintf((char*)strLogMsg.data(), strLogMsg.capacity(), pszFmt, aq);
va_end(aq);
//string内容正确但length不对,恢复一下其length
std::string strMsgFormal;
strMsgFormal.append(strLogMsg.c_str(), nLogMsgLength);
//如果日志开启截断,长日志只取前MAX_LINE_LENGTH个字符
if (m_bTruncateLongLog)
strMsgFormal = strMsgFormal.substr(0, MAX_LINE_LENGTH);
strLine += strMsgFormal;
//不是输出到控制台才会在每一行末尾加一个换行符
if (!m_strFileName.empty())
{
strLine += "\n";
}
if (nLevel != LOG_LEVEL_FATAL)
{
std::lock_guard<std::mutex> lock_guard(m_mutexWrite);
m_listLinesToWrite.push_back(strLine);
m_cvWrite.notify_one();
}
else
{
//为了让FATAL级别的日志能立即crash程序,采取同步写日志的方法
std::cout << strLine << std::endl;
#ifdef _WIN32
OutputDebugStringA(strLine.c_str());
OutputDebugStringA("\n");
#endif
if (!m_strFileName.empty())
{
if (m_hLogFile == nullptr)
{
//新建文件
char szNow[64];
time_t now = time(NULL);
tm time;
#ifdef _WIN32
localtime_s(&time, &now);
#else
localtime_r(&now, &time);
#endif
strftime(szNow, sizeof(szNow), "%Y%m%d%H%M%S", &time);
std::string strNewFileName(m_strFileName);
strNewFileName += ".";
strNewFileName += szNow;
strNewFileName += ".";
strNewFileName += m_strFileNamePID;
strNewFileName += ".log";
if (!createNewFile(strNewFileName.c_str()))
return false;
}// end inner if
writeToFile(strLine);
}// end outer-if
//让程序主动crash掉
crash();
}
return true;
}
bool CAsyncLog::outputBinary(unsigned char* buffer, size_t size)
{
//std::string strBinary;
std::ostringstream os;
static const size_t PRINTSIZE = 512;
char szbuf[PRINTSIZE * 3 + 8];
size_t lsize = 0;
size_t lprintbufsize = 0;
int index = 0;
os << "address[" << (long)buffer << "] size[" << size << "] \n";
while (true)
{
memset(szbuf, 0, sizeof(szbuf));
if (size > lsize)
{
lprintbufsize = (size - lsize);
lprintbufsize = lprintbufsize > PRINTSIZE ? PRINTSIZE : lprintbufsize;
formLog(index, szbuf, sizeof(szbuf), buffer + lsize, lprintbufsize);
size_t len = strlen(szbuf);
//if (stream().buffer().avail() < static_cast<int>(len))
//{
// stream() << szbuf + (len - stream().buffer().avail());
// break;
//}
//else
//{
os << szbuf;
//}
lsize += lprintbufsize;
}
else
{
break;
}
}
std::lock_guard<std::mutex> lock_guard(m_mutexWrite);
m_listLinesToWrite.push_back(os.str());
m_cvWrite.notify_one();
return true;
}
const char* CAsyncLog::ullto4Str(int n)
{
static char buf[64 + 1];
memset(buf, 0, sizeof(buf));
sprintf(buf, "%06u", n);
return buf;
}
char* CAsyncLog::formLog(int& index, char* szbuf, size_t size_buf, unsigned char* buffer, size_t size)
{
size_t len = 0;
size_t lsize = 0;
int headlen = 0;
char szhead[64 + 1] = { 0 };
char szchar[17] = "0123456789abcdef";
//memset(szhead, 0, sizeof(szhead));
while (size > lsize && len + 10 < size_buf)
{
if (lsize % 32 == 0)
{
if (0 != headlen)
{
szbuf[len++] = '\n';
}
memset(szhead, 0, sizeof(szhead));
strncpy(szhead, ullto4Str(index++), sizeof(szhead) - 1);
headlen = strlen(szhead);
szhead[headlen++] = ' ';
strcat(szbuf, szhead);
len += headlen;
}
if (lsize % 16 == 0 && 0 != headlen)
szbuf[len++] = ' ';
szbuf[len++] = szchar[(buffer[lsize] >> 4) & 0xf];
szbuf[len++] = szchar[(buffer[lsize]) & 0xf];
lsize++;
}
szbuf[len++] = '\n';
szbuf[len++] = '\0';
return szbuf;
}
void CAsyncLog::makeLinePrefix(long nLevel, std::string& strPrefix)
{
//级别
strPrefix = "[INFO]";
if (nLevel == LOG_LEVEL_TRACE)
strPrefix = "[TRACE]";
else if (nLevel == LOG_LEVEL_DEBUG)
strPrefix = "[DEBUG]";
else if (nLevel == LOG_LEVEL_WARNING)
strPrefix = "[WARN]";
else if (nLevel == LOG_LEVEL_ERROR)
strPrefix = "[ERROR]";
else if (nLevel == LOG_LEVEL_SYSERROR)
strPrefix = "[SYSE]";
else if (nLevel == LOG_LEVEL_FATAL)
strPrefix = "[FATAL]";
else if (nLevel == LOG_LEVEL_CRITICAL)
strPrefix = "[CRITICAL]";
//时间
char szTime[64] = { 0 };
getTime(szTime, sizeof(szTime));
strPrefix += "[";
strPrefix += szTime;
strPrefix += "]";
//当前线程信息
char szThreadID[32] = { 0 };
std::ostringstream osThreadID;
osThreadID << std::this_thread::get_id();
snprintf(szThreadID, sizeof(szThreadID), "[%s]", osThreadID.str().c_str());
strPrefix += szThreadID;
}
void CAsyncLog::getTime(char* pszTime, int nTimeStrLength)
{
struct timeb tp;
ftime(&tp);
time_t now = tp.time;
tm time;
#ifdef _WIN32
localtime_s(&time, &now);
#else
localtime_r(&now, &time);
#endif
snprintf(pszTime, nTimeStrLength, "[%04d-%02d-%02d %02d:%02d:%02d:%03d]", time.tm_year + 1900, time.tm_mon + 1, time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec, tp.millitm);
//strftime(pszTime, nTimeStrLength, "[%Y-%m-%d %H:%M:%S:]", &time);
}
bool CAsyncLog::createNewFile(const char* pszLogFileName)
{
if (m_hLogFile != nullptr)
{
fclose(m_hLogFile);
}
//始终新建文件
m_hLogFile = fopen(pszLogFileName, "w+");
return m_hLogFile != nullptr;
}
bool CAsyncLog::writeToFile(const std::string& data)
{
//为了防止长文件一次性写不完,放在一个循环里面分批写
std::string strLocal(data);
int ret = 0;
while (true)
{
ret = fwrite(strLocal.c_str(), 1, strLocal.length(), m_hLogFile);
if (ret <= 0)
return false;
else if (ret <= (int)strLocal.length())
{
strLocal.erase(0, ret);
}
if (strLocal.empty())
break;
}
//::OutputDebugStringA(strDebugInfo.c_str());
fflush(m_hLogFile);
return true;
}
void CAsyncLog::crash()
{
char* p = nullptr;
*p = 0;
}
void CAsyncLog::writeThreadProc()
{
m_bRunning = true;
while (true)
{
if (!m_strFileName.empty())
{
if (m_hLogFile == nullptr || m_nCurrentWrittenSize >= m_nFileRollSize)
{
//重置m_nCurrentWrittenSize大小
m_nCurrentWrittenSize = 0;
//第一次或者文件大小超过rollsize,均新建文件
char szNow[64];
time_t now = time(NULL);
tm time;
#ifdef _WIN32
localtime_s(&time, &now);
#else
localtime_r(&now, &time);
#endif
strftime(szNow, sizeof(szNow), "%Y%m%d%H%M%S", &time);
std::string strNewFileName(m_strFileName);
strNewFileName += ".";
strNewFileName += szNow;
strNewFileName += ".";
strNewFileName += m_strFileNamePID;
strNewFileName += ".log";
if (!createNewFile(strNewFileName.c_str()))
return;
}// end inner if
}// end outer-if
std::string strLine;
{
std::unique_lock<std::mutex> guard(m_mutexWrite);
while (m_listLinesToWrite.empty())
{
if (m_bExit)
return;
m_cvWrite.wait(guard);
}
strLine = m_listLinesToWrite.front();
m_listLinesToWrite.pop_front();
}
std::cout << strLine << std::endl;
#ifdef _WIN32
OutputDebugStringA(strLine.c_str());
OutputDebugStringA("\n");
#endif
if (!m_strFileName.empty())
{
if (!writeToFile(strLine))
return;
m_nCurrentWrittenSize += strLine.length();
}
}// end outer-while-loop
m_bRunning = false;
}
3、应用
#ifdef _DEBUG
CAsyncLog::init();
#else
CAsyncLog::init(logFileFullPath.c_str());
#endif
LOGI("exit chatserver.");
LOGI("current online user count: %d", (int)m_sessions.size());