1、c++异步日志

0、思维导图

日志 - 图1

1、头文件

  1. /**
  2. * @desc: 异步日志类,AsyncLog.h
  3. * @author: zhangyl
  4. * @date: 2019.04.13
  5. */
  6. #ifndef __ASYNC_LOG_H__
  7. #define __ASYNC_LOG_H__
  8. #include <stdio.h>
  9. #include <string>
  10. #include <list>
  11. #include <thread>
  12. #include <memory>
  13. #include <mutex>
  14. #include <condition_variable>
  15. //#ifdef LOG_EXPORTS
  16. //#define LOG_API __declspec(dllexport)
  17. //#else
  18. //#define LOG_API __declspec(dllimport)
  19. //#endif
  20. #define LOG_API
  21. enum LOG_LEVEL
  22. {
  23. LOG_LEVEL_TRACE,
  24. LOG_LEVEL_DEBUG,
  25. LOG_LEVEL_INFO,
  26. LOG_LEVEL_WARNING,
  27. LOG_LEVEL_ERROR, //用于业务错误
  28. LOG_LEVEL_SYSERROR, //用于技术框架本身的错误
  29. LOG_LEVEL_FATAL, //FATAL 级别的日志会让在程序输出日志后退出
  30. LOG_LEVEL_CRITICAL //CRITICAL 日志不受日志级别控制,总是输出
  31. };
  32. //TODO: 多增加几个策略
  33. //注意:如果打印的日志信息中有中文,则格式化字符串要用_T()宏包裹起来,
  34. //e.g. LOGI(_T("GroupID=%u, GroupName=%s, GroupName=%s."), lpGroupInfo->m_nGroupCode, lpGroupInfo->m_strAccount.c_str(), lpGroupInfo->m_strName.c_str());
  35. #define LOGT(...) CAsyncLog::output(LOG_LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__)
  36. #define LOGD(...) CAsyncLog::output(LOG_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
  37. #define LOGI(...) CAsyncLog::output(LOG_LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__)
  38. #define LOGW(...) CAsyncLog::output(LOG_LEVEL_WARNING, __FILE__, __LINE__,__VA_ARGS__)
  39. #define LOGE(...) CAsyncLog::output(LOG_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__)
  40. #define LOGSYSE(...) CAsyncLog::output(LOG_LEVEL_SYSERROR, __FILE__, __LINE__, __VA_ARGS__)
  41. #define LOGF(...) CAsyncLog::output(LOG_LEVEL_FATAL, __FILE__, __LINE__, __VA_ARGS__) //为了让FATAL级别的日志能立即crash程序,采取同步写日志的方法
  42. #define LOGC(...) CAsyncLog::output(LOG_LEVEL_CRITICAL, __FILE__, __LINE__, __VA_ARGS__) //关键信息,无视日志级别,总是输出
  43. //用于输出数据包的二进制格式
  44. #define LOG_DEBUG_BIN(buf, buflength) CAsyncLog::outputBinary(buf, buflength)
  45. class LOG_API CAsyncLog
  46. {
  47. public:
  48. static bool init(const char* pszLogFileName = nullptr, bool bTruncateLongLine = false, int64_t nRollSize = 10 * 1024 * 1024);
  49. static void uninit();
  50. static void setLevel(LOG_LEVEL nLevel);
  51. static bool isRunning();
  52. //不输出线程ID号和所在函数签名、行号
  53. static bool output(long nLevel, const char* pszFmt, ...);
  54. //输出线程ID号和所在函数签名、行号
  55. static bool output(long nLevel, const char* pszFileName, int nLineNo, const char* pszFmt, ...);
  56. static bool outputBinary(unsigned char* buffer, size_t size);
  57. private:
  58. CAsyncLog() = delete;
  59. ~CAsyncLog() = delete;
  60. CAsyncLog(const CAsyncLog& rhs) = delete;
  61. CAsyncLog& operator=(const CAsyncLog& rhs) = delete;
  62. static void makeLinePrefix(long nLevel, std::string& strPrefix);
  63. static void getTime(char* pszTime, int nTimeStrLength);
  64. static bool createNewFile(const char* pszLogFileName);
  65. static bool writeToFile(const std::string& data);
  66. //让程序主动崩溃
  67. static void crash();
  68. static const char* ullto4Str(int n);
  69. static char* formLog(int& index, char* szbuf, size_t size_buf, unsigned char* buffer, size_t size);
  70. static void writeThreadProc();
  71. private:
  72. static bool m_bToFile; //日志写入文件还是写到控制台
  73. static FILE* m_hLogFile;
  74. static std::string m_strFileName; //日志文件名
  75. static std::string m_strFileNamePID; //文件名中的进程id
  76. static bool m_bTruncateLongLog; //长日志是否截断
  77. static LOG_LEVEL m_nCurrentLevel; //当前日志级别
  78. static int64_t m_nFileRollSize; //单个日志文件的最大字节数
  79. static int64_t m_nCurrentWrittenSize; //已经写入的字节数目
  80. static std::list<std::string> m_listLinesToWrite; //待写入的日志
  81. static std::unique_ptr<std::thread> m_spWriteThread;
  82. static std::mutex m_mutexWrite;
  83. static std::condition_variable m_cvWrite;
  84. static bool m_bExit; //退出标志
  85. static bool m_bRunning; //运行标志
  86. };
  87. #endif // !__ASYNC_LOG_H__

2、cpp文件

  1. /**
  2. * @desc: 异步日志类,AsyncLog.cpp
  3. * @author: zhangyl
  4. * @date: 2019.04.13
  5. */
  6. #include "AsyncLog.h"
  7. #include <ctime>
  8. #include <time.h>
  9. #include <sys/timeb.h>
  10. #include <stdio.h>
  11. #include <string.h>
  12. #include <sstream>
  13. #include <iostream>
  14. #include <stdarg.h>
  15. #include "../base/Platform.h"
  16. #define MAX_LINE_LENGTH 256
  17. #define DEFAULT_ROLL_SIZE 10 * 1024 * 1024
  18. bool CAsyncLog::m_bTruncateLongLog = false;
  19. FILE* CAsyncLog::m_hLogFile = NULL;
  20. std::string CAsyncLog::m_strFileName = "default";
  21. std::string CAsyncLog::m_strFileNamePID = "";
  22. LOG_LEVEL CAsyncLog::m_nCurrentLevel = LOG_LEVEL_INFO;
  23. int64_t CAsyncLog::m_nFileRollSize = DEFAULT_ROLL_SIZE;
  24. int64_t CAsyncLog::m_nCurrentWrittenSize = 0;
  25. std::list<std::string> CAsyncLog::m_listLinesToWrite;
  26. std::unique_ptr<std::thread> CAsyncLog::m_spWriteThread;
  27. std::mutex CAsyncLog::m_mutexWrite;
  28. std::condition_variable CAsyncLog::m_cvWrite;
  29. bool CAsyncLog::CAsyncLog::m_bExit = false;
  30. bool CAsyncLog::m_bRunning = false;
  31. bool CAsyncLog::init(const char* pszLogFileName/* = nullptr*/, bool bTruncateLongLine/* = false*/, int64_t nRollSize/* = 10 * 1024 * 1024*/)
  32. {
  33. m_bTruncateLongLog = bTruncateLongLine;
  34. m_nFileRollSize = nRollSize;
  35. if (pszLogFileName == nullptr || pszLogFileName[0] == 0)
  36. {
  37. m_strFileName.clear();
  38. }
  39. else
  40. m_strFileName = pszLogFileName;
  41. //获取进程id,这样快速看到同一个进程的不同日志文件
  42. char szPID[8];
  43. #ifdef WIN32
  44. snprintf(szPID, sizeof(szPID), "%05d", (int)::GetCurrentProcessId());
  45. #else
  46. snprintf(szPID, sizeof(szPID), "%05d", (int)::getpid());
  47. #endif
  48. m_strFileNamePID = szPID;
  49. //TODO:创建文件夹
  50. m_spWriteThread.reset(new std::thread(writeThreadProc));
  51. return true;
  52. }
  53. void CAsyncLog::uninit()
  54. {
  55. m_bExit = true;
  56. m_cvWrite.notify_one();
  57. if (m_spWriteThread->joinable())
  58. m_spWriteThread->join();
  59. if(m_hLogFile != nullptr)
  60. {
  61. fclose(m_hLogFile);
  62. m_hLogFile = nullptr;
  63. }
  64. }
  65. void CAsyncLog::setLevel(LOG_LEVEL nLevel)
  66. {
  67. if (nLevel < LOG_LEVEL_TRACE || nLevel > LOG_LEVEL_FATAL)
  68. return;
  69. m_nCurrentLevel = nLevel;
  70. }
  71. bool CAsyncLog::isRunning()
  72. {
  73. return m_bRunning;
  74. }
  75. bool CAsyncLog::output(long nLevel, const char* pszFmt, ...)
  76. {
  77. if (nLevel != LOG_LEVEL_CRITICAL)
  78. {
  79. if (nLevel < m_nCurrentLevel)
  80. return false;
  81. }
  82. std::string strLine;
  83. makeLinePrefix(nLevel, strLine);
  84. //log正文
  85. std::string strLogMsg;
  86. //先计算一下不定参数的长度,以便于分配空间
  87. va_list ap;
  88. va_start(ap, pszFmt);
  89. int nLogMsgLength = vsnprintf(NULL, 0, pszFmt, ap);
  90. va_end(ap);
  91. //容量必须算上最后一个\0
  92. if ((int)strLogMsg.capacity() < nLogMsgLength + 1)
  93. {
  94. strLogMsg.resize(nLogMsgLength + 1);
  95. }
  96. va_list aq;
  97. va_start(aq, pszFmt);
  98. vsnprintf((char*)strLogMsg.data(), strLogMsg.capacity(), pszFmt, aq);
  99. va_end(aq);
  100. //string内容正确但length不对,恢复一下其length
  101. std::string strMsgFormal;
  102. strMsgFormal.append(strLogMsg.c_str(), nLogMsgLength);
  103. //如果日志开启截断,长日志只取前MAX_LINE_LENGTH个字符
  104. if (m_bTruncateLongLog)
  105. strMsgFormal = strMsgFormal.substr(0, MAX_LINE_LENGTH);
  106. strLine += strMsgFormal;
  107. //不是输出到控制台才会在每一行末尾加一个换行符
  108. if (!m_strFileName.empty())
  109. {
  110. strLine += "\n";
  111. }
  112. if (nLevel != LOG_LEVEL_FATAL)
  113. {
  114. std::lock_guard<std::mutex> lock_guard(m_mutexWrite);
  115. m_listLinesToWrite.push_back(strLine);
  116. m_cvWrite.notify_one();
  117. }
  118. else
  119. {
  120. //为了让FATAL级别的日志能立即crash程序,采取同步写日志的方法
  121. std::cout << strLine << std::endl;
  122. #ifdef _WIN32
  123. OutputDebugStringA(strLine.c_str());
  124. OutputDebugStringA("\n");
  125. #endif
  126. if (!m_strFileName.empty())
  127. {
  128. if (m_hLogFile == nullptr)
  129. {
  130. //新建文件
  131. char szNow[64];
  132. time_t now = time(NULL);
  133. tm time;
  134. #ifdef _WIN32
  135. localtime_s(&time, &now);
  136. #else
  137. localtime_r(&now, &time);
  138. #endif
  139. strftime(szNow, sizeof(szNow), "%Y%m%d%H%M%S", &time);
  140. std::string strNewFileName(m_strFileName);
  141. strNewFileName += ".";
  142. strNewFileName += szNow;
  143. strNewFileName += ".";
  144. strNewFileName += m_strFileNamePID;
  145. strNewFileName += ".log";
  146. if (!createNewFile(strNewFileName.c_str()))
  147. return false;
  148. }// end inner if
  149. writeToFile(strLine);
  150. }// end outer-if
  151. //让程序主动crash掉
  152. crash();
  153. }
  154. return true;
  155. }
  156. bool CAsyncLog::output(long nLevel, const char* pszFileName, int nLineNo, const char* pszFmt, ...)
  157. {
  158. if (nLevel != LOG_LEVEL_CRITICAL)
  159. {
  160. if (nLevel < m_nCurrentLevel)
  161. return false;
  162. }
  163. std::string strLine;
  164. makeLinePrefix(nLevel, strLine);
  165. //函数签名
  166. char szFileName[512] = { 0 };
  167. snprintf(szFileName, sizeof(szFileName), "[%s:%d]", pszFileName, nLineNo);
  168. strLine += szFileName;
  169. //日志正文
  170. std::string strLogMsg;
  171. //先计算一下不定参数的长度,以便于分配空间
  172. va_list ap;
  173. va_start(ap, pszFmt);
  174. int nLogMsgLength = vsnprintf(NULL, 0, pszFmt, ap);
  175. va_end(ap);
  176. //容量必须算上最后一个\0
  177. if ((int)strLogMsg.capacity() < nLogMsgLength + 1)
  178. {
  179. strLogMsg.resize(nLogMsgLength + 1);
  180. }
  181. va_list aq;
  182. va_start(aq, pszFmt);
  183. vsnprintf((char*)strLogMsg.data(), strLogMsg.capacity(), pszFmt, aq);
  184. va_end(aq);
  185. //string内容正确但length不对,恢复一下其length
  186. std::string strMsgFormal;
  187. strMsgFormal.append(strLogMsg.c_str(), nLogMsgLength);
  188. //如果日志开启截断,长日志只取前MAX_LINE_LENGTH个字符
  189. if (m_bTruncateLongLog)
  190. strMsgFormal = strMsgFormal.substr(0, MAX_LINE_LENGTH);
  191. strLine += strMsgFormal;
  192. //不是输出到控制台才会在每一行末尾加一个换行符
  193. if (!m_strFileName.empty())
  194. {
  195. strLine += "\n";
  196. }
  197. if (nLevel != LOG_LEVEL_FATAL)
  198. {
  199. std::lock_guard<std::mutex> lock_guard(m_mutexWrite);
  200. m_listLinesToWrite.push_back(strLine);
  201. m_cvWrite.notify_one();
  202. }
  203. else
  204. {
  205. //为了让FATAL级别的日志能立即crash程序,采取同步写日志的方法
  206. std::cout << strLine << std::endl;
  207. #ifdef _WIN32
  208. OutputDebugStringA(strLine.c_str());
  209. OutputDebugStringA("\n");
  210. #endif
  211. if (!m_strFileName.empty())
  212. {
  213. if (m_hLogFile == nullptr)
  214. {
  215. //新建文件
  216. char szNow[64];
  217. time_t now = time(NULL);
  218. tm time;
  219. #ifdef _WIN32
  220. localtime_s(&time, &now);
  221. #else
  222. localtime_r(&now, &time);
  223. #endif
  224. strftime(szNow, sizeof(szNow), "%Y%m%d%H%M%S", &time);
  225. std::string strNewFileName(m_strFileName);
  226. strNewFileName += ".";
  227. strNewFileName += szNow;
  228. strNewFileName += ".";
  229. strNewFileName += m_strFileNamePID;
  230. strNewFileName += ".log";
  231. if (!createNewFile(strNewFileName.c_str()))
  232. return false;
  233. }// end inner if
  234. writeToFile(strLine);
  235. }// end outer-if
  236. //让程序主动crash掉
  237. crash();
  238. }
  239. return true;
  240. }
  241. bool CAsyncLog::outputBinary(unsigned char* buffer, size_t size)
  242. {
  243. //std::string strBinary;
  244. std::ostringstream os;
  245. static const size_t PRINTSIZE = 512;
  246. char szbuf[PRINTSIZE * 3 + 8];
  247. size_t lsize = 0;
  248. size_t lprintbufsize = 0;
  249. int index = 0;
  250. os << "address[" << (long)buffer << "] size[" << size << "] \n";
  251. while (true)
  252. {
  253. memset(szbuf, 0, sizeof(szbuf));
  254. if (size > lsize)
  255. {
  256. lprintbufsize = (size - lsize);
  257. lprintbufsize = lprintbufsize > PRINTSIZE ? PRINTSIZE : lprintbufsize;
  258. formLog(index, szbuf, sizeof(szbuf), buffer + lsize, lprintbufsize);
  259. size_t len = strlen(szbuf);
  260. //if (stream().buffer().avail() < static_cast<int>(len))
  261. //{
  262. // stream() << szbuf + (len - stream().buffer().avail());
  263. // break;
  264. //}
  265. //else
  266. //{
  267. os << szbuf;
  268. //}
  269. lsize += lprintbufsize;
  270. }
  271. else
  272. {
  273. break;
  274. }
  275. }
  276. std::lock_guard<std::mutex> lock_guard(m_mutexWrite);
  277. m_listLinesToWrite.push_back(os.str());
  278. m_cvWrite.notify_one();
  279. return true;
  280. }
  281. const char* CAsyncLog::ullto4Str(int n)
  282. {
  283. static char buf[64 + 1];
  284. memset(buf, 0, sizeof(buf));
  285. sprintf(buf, "%06u", n);
  286. return buf;
  287. }
  288. char* CAsyncLog::formLog(int& index, char* szbuf, size_t size_buf, unsigned char* buffer, size_t size)
  289. {
  290. size_t len = 0;
  291. size_t lsize = 0;
  292. int headlen = 0;
  293. char szhead[64 + 1] = { 0 };
  294. char szchar[17] = "0123456789abcdef";
  295. //memset(szhead, 0, sizeof(szhead));
  296. while (size > lsize && len + 10 < size_buf)
  297. {
  298. if (lsize % 32 == 0)
  299. {
  300. if (0 != headlen)
  301. {
  302. szbuf[len++] = '\n';
  303. }
  304. memset(szhead, 0, sizeof(szhead));
  305. strncpy(szhead, ullto4Str(index++), sizeof(szhead) - 1);
  306. headlen = strlen(szhead);
  307. szhead[headlen++] = ' ';
  308. strcat(szbuf, szhead);
  309. len += headlen;
  310. }
  311. if (lsize % 16 == 0 && 0 != headlen)
  312. szbuf[len++] = ' ';
  313. szbuf[len++] = szchar[(buffer[lsize] >> 4) & 0xf];
  314. szbuf[len++] = szchar[(buffer[lsize]) & 0xf];
  315. lsize++;
  316. }
  317. szbuf[len++] = '\n';
  318. szbuf[len++] = '\0';
  319. return szbuf;
  320. }
  321. void CAsyncLog::makeLinePrefix(long nLevel, std::string& strPrefix)
  322. {
  323. //级别
  324. strPrefix = "[INFO]";
  325. if (nLevel == LOG_LEVEL_TRACE)
  326. strPrefix = "[TRACE]";
  327. else if (nLevel == LOG_LEVEL_DEBUG)
  328. strPrefix = "[DEBUG]";
  329. else if (nLevel == LOG_LEVEL_WARNING)
  330. strPrefix = "[WARN]";
  331. else if (nLevel == LOG_LEVEL_ERROR)
  332. strPrefix = "[ERROR]";
  333. else if (nLevel == LOG_LEVEL_SYSERROR)
  334. strPrefix = "[SYSE]";
  335. else if (nLevel == LOG_LEVEL_FATAL)
  336. strPrefix = "[FATAL]";
  337. else if (nLevel == LOG_LEVEL_CRITICAL)
  338. strPrefix = "[CRITICAL]";
  339. //时间
  340. char szTime[64] = { 0 };
  341. getTime(szTime, sizeof(szTime));
  342. strPrefix += "[";
  343. strPrefix += szTime;
  344. strPrefix += "]";
  345. //当前线程信息
  346. char szThreadID[32] = { 0 };
  347. std::ostringstream osThreadID;
  348. osThreadID << std::this_thread::get_id();
  349. snprintf(szThreadID, sizeof(szThreadID), "[%s]", osThreadID.str().c_str());
  350. strPrefix += szThreadID;
  351. }
  352. void CAsyncLog::getTime(char* pszTime, int nTimeStrLength)
  353. {
  354. struct timeb tp;
  355. ftime(&tp);
  356. time_t now = tp.time;
  357. tm time;
  358. #ifdef _WIN32
  359. localtime_s(&time, &now);
  360. #else
  361. localtime_r(&now, &time);
  362. #endif
  363. 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);
  364. //strftime(pszTime, nTimeStrLength, "[%Y-%m-%d %H:%M:%S:]", &time);
  365. }
  366. bool CAsyncLog::createNewFile(const char* pszLogFileName)
  367. {
  368. if (m_hLogFile != nullptr)
  369. {
  370. fclose(m_hLogFile);
  371. }
  372. //始终新建文件
  373. m_hLogFile = fopen(pszLogFileName, "w+");
  374. return m_hLogFile != nullptr;
  375. }
  376. bool CAsyncLog::writeToFile(const std::string& data)
  377. {
  378. //为了防止长文件一次性写不完,放在一个循环里面分批写
  379. std::string strLocal(data);
  380. int ret = 0;
  381. while (true)
  382. {
  383. ret = fwrite(strLocal.c_str(), 1, strLocal.length(), m_hLogFile);
  384. if (ret <= 0)
  385. return false;
  386. else if (ret <= (int)strLocal.length())
  387. {
  388. strLocal.erase(0, ret);
  389. }
  390. if (strLocal.empty())
  391. break;
  392. }
  393. //::OutputDebugStringA(strDebugInfo.c_str());
  394. fflush(m_hLogFile);
  395. return true;
  396. }
  397. void CAsyncLog::crash()
  398. {
  399. char* p = nullptr;
  400. *p = 0;
  401. }
  402. void CAsyncLog::writeThreadProc()
  403. {
  404. m_bRunning = true;
  405. while (true)
  406. {
  407. if (!m_strFileName.empty())
  408. {
  409. if (m_hLogFile == nullptr || m_nCurrentWrittenSize >= m_nFileRollSize)
  410. {
  411. //重置m_nCurrentWrittenSize大小
  412. m_nCurrentWrittenSize = 0;
  413. //第一次或者文件大小超过rollsize,均新建文件
  414. char szNow[64];
  415. time_t now = time(NULL);
  416. tm time;
  417. #ifdef _WIN32
  418. localtime_s(&time, &now);
  419. #else
  420. localtime_r(&now, &time);
  421. #endif
  422. strftime(szNow, sizeof(szNow), "%Y%m%d%H%M%S", &time);
  423. std::string strNewFileName(m_strFileName);
  424. strNewFileName += ".";
  425. strNewFileName += szNow;
  426. strNewFileName += ".";
  427. strNewFileName += m_strFileNamePID;
  428. strNewFileName += ".log";
  429. if (!createNewFile(strNewFileName.c_str()))
  430. return;
  431. }// end inner if
  432. }// end outer-if
  433. std::string strLine;
  434. {
  435. std::unique_lock<std::mutex> guard(m_mutexWrite);
  436. while (m_listLinesToWrite.empty())
  437. {
  438. if (m_bExit)
  439. return;
  440. m_cvWrite.wait(guard);
  441. }
  442. strLine = m_listLinesToWrite.front();
  443. m_listLinesToWrite.pop_front();
  444. }
  445. std::cout << strLine << std::endl;
  446. #ifdef _WIN32
  447. OutputDebugStringA(strLine.c_str());
  448. OutputDebugStringA("\n");
  449. #endif
  450. if (!m_strFileName.empty())
  451. {
  452. if (!writeToFile(strLine))
  453. return;
  454. m_nCurrentWrittenSize += strLine.length();
  455. }
  456. }// end outer-while-loop
  457. m_bRunning = false;
  458. }

3、应用

  1. #ifdef _DEBUG
  2. CAsyncLog::init();
  3. #else
  4. CAsyncLog::init(logFileFullPath.c_str());
  5. #endif
  6. LOGI("exit chatserver.");
  7. LOGI("current online user count: %d", (int)m_sessions.size());