• C++类设计:一个比较复杂的日志类 支持多线程、文件切换、信息缓存(源码)


    初级代码游戏的专栏介绍与文章目录-CSDN博客

    github位置:codetoys/ctfc.git src/env/myLog.h和.cpp  


            这个类功能细节比较多,因为是长期使用的版本,累积了各种功能。之前介绍的两个是我的测试代码用的版本,非常简单,那两个在这里:

    C++类设计:设计一个日志类(源码)_初级代码游戏的博客-CSDN博客

    C++类设计:一个不同版本的日志类(完整源码)_初级代码游戏的博客-CSDN博客

            其实这个版本跟上面两个很不一样,这个版本是基于unix/linux的(64位),控制台程序,支持多线程(需要链接pthread),支持日志文件切换,统计各种信息的条数,能缓存信息,可用于如果一段代码正确结束就不输出、出错才输出。

    目录

    一、完整源码

    二、详解

    2.1 关于endi、ende、endr

    2.2 多线程支持

    2.3 缓存支持

    2.4 日志切换


    一、完整源码

            头文件:

    1. //输出信息所在位置的日志宏------g_pEnv->GetLog()返回Log,可以直接定义为全局变量
    2. #define thelog ((g_pEnv->GetLog()).LogPos(__FILE__,__LINE__,__func__))
    3. #define theLog (g_pEnv->GetLog()) //不带文件名行号函数名
    4. //输出调试信息的宏,G_DEBUG G_TRANCE可以直接定义为全局变量
    5. #define DEBUG_LOG if(G_DEBUG)thelog
    6. #define TRANCE_LOG if(G_TRANCE)thelog
    7. //
    8. //日至
    9. struct LogEnd
    10. {
    11. enum { fENDE = 1, fENDW, fENDI, fENDF, fENDD };
    12. char end;
    13. LogEnd(int ,int n):end(n){}
    14. };
    15. ostream & operator<<(ostream & s, LogEnd const & end);//这个好像没啥用,但是我暂时没验证,不敢删
    16. LogEnd const ende(0, LogEnd::fENDE); // error
    17. LogEnd const endw(0, LogEnd::fENDW); // warning
    18. LogEnd const endi(0, LogEnd::fENDI); // information
    19. LogEnd const endf(0, LogEnd::fENDF); // fatal
    20. LogEnd const endd(0, LogEnd::fENDD); // debug
    21. class Log
    22. {
    23. private:
    24. //线程数据
    25. struct _ThreadSpec
    26. {
    27. long m_thread_id;
    28. stringstream m_buf;
    29. string m_strSource;
    30. bool m_bOutputThis;//仅控制当前一行输出
    31. string m__file;
    32. long m__line;
    33. string m__func;
    34. _ThreadSpec() :m_thread_id(0), m_bOutputThis(true), m__line(0) {}
    35. };
    36. static void _CloseThreadSpec(void * _p)
    37. {
    38. _ThreadSpec * p=(_ThreadSpec *)_p;
    39. delete p;
    40. }
    41. _ThreadSpec * _getThreadSpec()
    42. {
    43. _ThreadSpec * p = (_ThreadSpec *)pthread_getspecific(tsd_key);
    44. if (p)return p;
    45. else
    46. {
    47. p = new _ThreadSpec;
    48. if (!p)
    49. {
    50. throw "new _ThreadSpec return NULL";
    51. }
    52. m_mutex.lock();
    53. p->m_thread_id = m_thread_count;
    54. ++m_thread_count;
    55. m_mutex.unlock();
    56. if (0 == pthread_setspecific(tsd_key, p))
    57. {
    58. return p;
    59. }
    60. else
    61. {
    62. throw "pthread_setspecific error";
    63. }
    64. }
    65. }
    66. private:
    67. long m_thread_count;//线程计数
    68. pthread_key_t tsd_key;//线程存储key
    69. CPThreadMutex m_mutex;
    70. string m_appname;//用来构造m_filename,用于定时切换文件,若未设置则直接使用m_filename
    71. string m_appname_old;//切换日志时用来保存原来的,以便切换换回来
    72. string m_filename;//当前的日志文件名
    73. ofstream m_ofs;
    74. bool m_bOutput;//控制所有输出
    75. bool m_bCache;//当!m_bOutput时缓存输出结果
    76. string::size_type m_maxcache;//最大缓存的数目
    77. string m_cache;//缓存的输出
    78. void(*m_pUD)(const string& strLog);//用户定义的函数,输出的时候会执行一次,一般也不用
    79. //错误和警告统计
    80. long countw;
    81. long counte;
    82. long countall;
    83. //正常记录统计
    84. long countn;
    85. public:
    86. //设置用户定义的函数,输出的时候会执行一次,一般也不用
    87. void SetUserFunction(void(*pUD)(const string& strLog)) { m_pUD = pUD; }
    88. public:
    89. Log()
    90. {
    91. m_thread_count = 0;
    92. pthread_key_create(&tsd_key, _CloseThreadSpec);
    93. m_mutex.init();
    94. SetSource("应用");
    95. m_bOutput = true;
    96. m_bCache = false;
    97. m_maxcache = 0;
    98. m_pUD = NULL;
    99. countw = 0;
    100. counte = 0;
    101. countn = 0;
    102. countall = 0;
    103. }
    104. void GetCount(long & c_w, long & c_e, long & c_all)const { c_w = countw; c_e = counte; c_all = countall; }
    105. string const &GetFileName()const { return m_filename; }
    106. Log& LogPos(char const * file, long line,char const * func)
    107. {
    108. string tmp = file;
    109. string::size_type pos = tmp.find_last_of("/");
    110. if (pos != tmp.npos)_getThreadSpec()->m__file = tmp.substr(pos + 1);
    111. else _getThreadSpec()->m__file = file;
    112. _getThreadSpec()->m__line = line;
    113. _getThreadSpec()->m__func = func;
    114. return *this;
    115. }
    116. string const & _makesource(string const & source, string & ret);
    117. template <typename T>
    118. Log& operator<<(T const& t)
    119. {
    120. _getThreadSpec()->m_buf << (t);
    121. return *this;
    122. }
    123. Log& operator<<(stringstream const& ss)
    124. {
    125. _getThreadSpec()->m_buf << ss.str();
    126. return *this;
    127. }
    128. Log & operator<<(ostream &(*p)(ostream &))
    129. {
    130. _getThreadSpec()->m_buf <<(p);
    131. return *this;
    132. }
    133. Log & operator<<(LogEnd const & end)
    134. {
    135. m_mutex.lock();
    136. _ThreadSpec * ThreadSpec = _getThreadSpec();
    137. char nCh = end.end;
    138. bool isImportant = false;
    139. string strType;
    140. switch (nCh)
    141. {
    142. case LogEnd::fENDI:
    143. strType = "[信息]";
    144. ++countn;
    145. break;
    146. case LogEnd::fENDW:
    147. strType = "[警告]";
    148. isImportant = true;
    149. ++countw;
    150. break;
    151. case LogEnd::fENDE:
    152. strType = "[出错]";
    153. isImportant = true;
    154. ++counte;
    155. break;
    156. case LogEnd::fENDF:
    157. strType = "[致命]";
    158. isImportant = true;
    159. break;
    160. case LogEnd::fENDD:
    161. strType = "[调试]";
    162. break;
    163. }
    164. if (isImportant)++countall;
    165. time_t t;
    166. tm const * t2;
    167. char buf[2048];
    168. time(&t);
    169. t2 = localtime(&t);
    170. sprintf(buf, "%02d-%02d %02d:%02d:%02d", t2->tm_mon + 1, t2->tm_mday, t2->tm_hour, t2->tm_min, t2->tm_sec);
    171. string strTime = buf;
    172. strTime = "[" + strTime + "]";
    173. //若未设置则不输出文件名和行号
    174. if (0 != ThreadSpec->m__file.size())
    175. {
    176. sprintf(buf, "[%-24s:%4ld(%s)][%6.2f]", ThreadSpec->m__file.c_str()
    177. , ThreadSpec->m__line, ThreadSpec->m__func.c_str(), (clock() / (float)CLOCKS_PER_SEC));
    178. }
    179. else buf[0] = '\0';
    180. string tmpSource;
    181. string strMsg;
    182. strMsg = strTime + _makesource(ThreadSpec->m_strSource, tmpSource) + strType + buf + ThreadSpec->m_buf.str();
    183. if (LogEnd::fENDE == nCh)
    184. {
    185. if (G_ERROR_MESSAGE().str().size() > 1024 * 1024)G_ERROR_MESSAGE().str(G_ERROR_MESSAGE().str().substr(512 * 1024));
    186. G_ERROR_MESSAGE() << strMsg << endl;
    187. }
    188. if (!m_bCache && m_filename.size() != 0)
    189. {
    190. string newfile = makelogfilename();
    191. if (m_filename != newfile)
    192. {
    193. m_ofs.close();
    194. _Open(newfile);
    195. cout << "文件切换,原文件 " << m_filename << " 新文件 " << newfile << endl;
    196. m_ofs << "文件切换,原文件 " << m_filename << " 新文件 " << newfile << endl;
    197. }
    198. m_ofs << strMsg.c_str() << endl;
    199. m_ofs.flush();
    200. if (m_ofs.bad())
    201. {
    202. m_ofs.close();
    203. _Open(newfile);
    204. cout << "写文件错误,关闭后重新打开文件" << endl;
    205. m_ofs << "写文件错误,关闭后重新打开文件" << endl;
    206. m_ofs << strMsg.c_str() << endl;
    207. m_ofs.flush();
    208. }
    209. }
    210. if (m_bOutput && ThreadSpec->m_bOutputThis)
    211. {
    212. cout << strMsg.c_str() << endl;
    213. }
    214. else
    215. {
    216. if (m_bCache)
    217. {
    218. if (m_cache.size() > m_maxcache)
    219. {
    220. m_cache.erase(0, m_cache.size() / 2);//超长时删去前半部分
    221. }
    222. m_cache += strMsg + "\n";
    223. }
    224. }
    225. ThreadSpec->m_bOutputThis = true;
    226. if (m_pUD) (*m_pUD)(strMsg); // 用户定义功能
    227. ThreadSpec->m__file = "";
    228. ThreadSpec->m__line = 0;
    229. ThreadSpec->m_buf.str("");
    230. m_mutex.unlock();
    231. return *this;
    232. }
    233. Log& SetSource(const string& strSource)
    234. {
    235. _getThreadSpec()->m_strSource = "[" + strSource + "]";
    236. return *this;
    237. }
    238. void setMaxFileSize(long)
    239. {
    240. cout << "theLog.setMaxFileSize(maxsize) 此功能已取消" << endl;
    241. }
    242. void setCache(string::size_type maxcache)
    243. {
    244. m_maxcache = maxcache;
    245. m_bCache = (maxcache > 0);
    246. }
    247. bool getCache()const { return m_bCache; }
    248. string & GetCachedLog(string & ret)//获得缓存的日志并清理缓存
    249. {
    250. ret = m_cache;
    251. m_cache.clear();
    252. return ret;
    253. }
    254. void ClearCache()//结束缓存,丢弃缓存的东西
    255. {
    256. m_cache.clear();
    257. setCache(0);
    258. }
    259. void setOutput(bool bEnable = true) { m_bOutput = bEnable; }
    260. bool getOutput() { return m_bOutput; }
    261. Log & setOutputThis(bool bEnable = false) { _getThreadSpec()->m_bOutputThis = bEnable; return *this; }
    262. #ifdef _LINUXOS
    263. int _Open(const string& strFile, std::_Ios_Openmode nMode = ios::out | ios::app)
    264. #else
    265. int _Open(const string& strFile, int nMode = ios::out | ios::app)
    266. #endif
    267. {
    268. m_ofs.close();
    269. m_ofs.clear();
    270. m_ofs.open(strFile.c_str(), nMode);
    271. if (!m_ofs.good())
    272. {
    273. cout << "打开文件出错 " << strFile << " " << strerror(errno) << endl;
    274. return -1;
    275. }
    276. m_filename = strFile;
    277. return 1;
    278. }
    279. //以固定文件方式打开日志,不会根据日期切换
    280. int Open(char const * filename)
    281. {
    282. cout << "theLog.Open(filename) 此操作将取消按日期生成日志文件的功能,若要使用按日期分文件请取消此调用" << endl;
    283. m_appname_old = m_appname;
    284. m_appname = "";
    285. return _Open(filename);
    286. }
    287. int ReturnToOldFile()
    288. {
    289. return ActiveOpen(m_appname_old.c_str());
    290. }
    291. //根据当前年月日构造日志文件名,若未设置m_appname则为m_filename
    292. string makelogfilename()
    293. {
    294. if (0 == m_appname.size())return m_filename;
    295. time_t t;
    296. tm const * t2;
    297. char buf[256];
    298. time(&t);
    299. t2 = localtime(&t);
    300. sprintf(buf, "%s.%04d%02d%02d.log", m_appname.c_str(), t2->tm_year + 1900, t2->tm_mon + 1, t2->tm_mday);
    301. return buf;
    302. }
    303. //按日期打开日志,日期改变日志文件自动切换,格式为“appname.YYYYMMDD.log”
    304. int ActiveOpen(char const * appname)
    305. {
    306. m_appname = appname;
    307. return _Open(makelogfilename());
    308. }
    309. //获得当前日志位置
    310. long tellEndP()
    311. {
    312. m_ofs.seekp(0, ios::end);
    313. return m_ofs.tellp();
    314. }
    315. //返回当前日志记录数
    316. long getCountN()
    317. {
    318. return countn;
    319. }
    320. long getCountW()
    321. {
    322. return countw;
    323. }
    324. long getCountE()
    325. {
    326. return counte;
    327. }
    328. };
    329. //日志接口
    330. #define LOG (thelog)
    331. #define ENDI (endi)
    332. #define ENDW (endw)
    333. #define ENDE (ende)

            源文件:

    1. //这个好像没啥用,但是我暂时没验证,不敢删
    2. ostream & operator<<(ostream & s, LogEnd const & end)
    3. {
    4. switch (end.end)
    5. {
    6. case LogEnd::fENDI:
    7. s << "[信息]";
    8. break;
    9. case LogEnd::fENDW:
    10. s << "[警告]";
    11. break;
    12. case LogEnd::fENDE:
    13. s << "[出错]";
    14. break;
    15. case LogEnd::fENDF:
    16. s << "[致命]";
    17. break;
    18. case LogEnd::fENDD:
    19. s << "[调试]";
    20. break;
    21. }
    22. s << endl;
    23. return s;
    24. }
    25. string const & Log::_makesource(string const & source, string & ret)
    26. {
    27. if (m_thread_count < 2)return source;
    28. _ThreadSpec * ThreadSpec = _getThreadSpec();
    29. char buf[256];
    30. if (0 == ThreadSpec->m_thread_id)
    31. {
    32. sprintf(buf, "[%lu]", (unsigned long)getpid());
    33. }
    34. else
    35. {
    36. sprintf(buf, "[%lu-%2ld]", (unsigned long)getpid(), ThreadSpec->m_thread_id);
    37. }
    38. return ret = source + buf;
    39. }

            代码里面用到了一个互斥对象,代码是这样的,简单包装而已:

    1. //线程同步对象
    2. class CPThreadMutex
    3. {
    4. private:
    5. pthread_mutex_t m_mutex;//互斥对象
    6. pthread_cond_t m_cond;//条件变量
    7. public:
    8. void init()
    9. {
    10. pthread_mutex_init(&m_mutex, NULL);
    11. pthread_cond_init(&m_cond, NULL);
    12. }
    13. void destory()
    14. {
    15. pthread_cond_destroy(&m_cond);
    16. pthread_mutex_destroy(&m_mutex);
    17. }
    18. int lock()const { return pthread_mutex_lock((pthread_mutex_t*)&m_mutex); }
    19. int unlock()const { return pthread_mutex_unlock((pthread_mutex_t*)&m_mutex); }
    20. int wait()const { return pthread_cond_wait((pthread_cond_t*)&m_cond, (pthread_mutex_t*)&m_mutex); }
    21. int reltimedwait()const
    22. {
    23. timespec to;
    24. to.tv_sec = time(NULL) + 1;
    25. to.tv_nsec = 0;
    26. int ret;
    27. ret = pthread_cond_timedwait((pthread_cond_t*)&m_cond, (pthread_mutex_t*)&m_mutex, &to);
    28. if (0 == ret)return true;
    29. else if (ETIMEDOUT == ret)return false;
    30. else throw ret;
    31. return false;
    32. }
    33. int signal()const { return pthread_cond_signal((pthread_cond_t*)&m_cond); }
    34. };

    二、详解

    2.1 关于endi、ende、endr

            这一套是传统留下来的,在结束输出的时候表明这是什么性质的日志。含义很简单:

            endi 信息

            endd 调试

            ende 出错

            endr 报告——由于endr和某些版本的oracle客户端冲突,再说也没有实际使用,所以就删掉了

            这个东西可以根据需要扩展。

    2.2 多线程支持

            由于日志对象是个全局对象,为了支持多线程使用,需要在内部实现多线程支持。具体办法是将临时缓存的数据放在线程存储里面。这里用的是POSIX线程库:

    1. //线程数据
    2. struct _ThreadSpec
    3. {
    4. long m_thread_id;
    5. stringstream m_buf;
    6. string m_strSource;
    7. bool m_bOutputThis;//仅控制当前一行输出
    8. string m__file;
    9. long m__line;
    10. string m__func;
    11. _ThreadSpec() :m_thread_id(0), m_bOutputThis(true), m__line(0) {}
    12. };
    13. static void _CloseThreadSpec(void * _p)
    14. {
    15. _ThreadSpec * p=(_ThreadSpec *)_p;
    16. delete p;
    17. }
    18. _ThreadSpec * _getThreadSpec()
    19. {
    20. _ThreadSpec * p = (_ThreadSpec *)pthread_getspecific(tsd_key);
    21. if (p)return p;
    22. else
    23. {
    24. p = new _ThreadSpec;
    25. if (!p)
    26. {
    27. throw "new _ThreadSpec return NULL";
    28. }
    29. m_mutex.lock();
    30. p->m_thread_id = m_thread_count;
    31. ++m_thread_count;
    32. m_mutex.unlock();
    33. if (0 == pthread_setspecific(tsd_key, p))
    34. {
    35. return p;
    36. }
    37. else
    38. {
    39. throw "pthread_setspecific error";
    40. }
    41. }
    42. }

            _getThreadSpec()用于获取线程数据,日志类的其他部分只用这个函数来操作数据。

            为了保证输出文件不混乱,在输出的时候使用了互斥锁。

    2.3 缓存支持

            如果执行没有失败,就没有必要输出详细日志。程序在开始一段操作时打开缓存,执行结束检查状态,成功就清除缓存,失败才输出缓存的日志。

    2.4 日志切换

            日志文件不能越滚越大,一般我们按天切换文件,在每次输出的时候检查日期是否已经改变,如果改变了就关闭文件再重新打开一个。

            日志太大搜索信息也很慢,一度我还用过这种策略:增加一个日志文件专门输出错误信息。

            日志处理的功能全看自己的需要。


    (这里是结束)

  • 相关阅读:
    Python使用psycopg2读取PostgreSQL的geometry字段出现二进制乱码
    如何愉快地搞定本科毕业论文、查重 / 计算机、人工智能、数据科学等专业
    校园报修维修小程序,微信小程序报修系统,微信小程序宿舍报修系统毕设作品
    【golang】探索for-range遍历实现原理(slice、map、channel)
    前端工程师都在用的 VSCode 常用插件
    Qt包含文件不存在问题解决 QNetworkAccessManager
    Android音视频——OMX 中 Nodeinstance 列表的管理与节点的操作
    【踩坑录】Javascript的预解析
    STL-list
    基于华为云IOT平台实现多节点温度采集(STM32+NBIOT)
  • 原文地址:https://blog.csdn.net/2301_77171572/article/details/132255518