C++还在用printf/cout进行Debug?学习一下如何自己写日志库吧(上篇)
文章目录
一. 前言
哈喽,自从实习以来很久没有更文了,一是没有时间,二是实习了之后突然发现自己能写的东西也没有多少了。赶上1024有征文活动,就写一篇吧,在实习的这段时间,我更加认识到日志的重要性,客户端值没传过来?看日志,服务崩溃了?看日志,没错,日志是出现异常第一个想到的东西,它记录了程序运行过程中所调用的函数,所接受到的值,所执行的行为等等。大家也都看到这篇的标题了,我这个人有一个缺点,就是不太喜欢用别人的东西,如果有能力,我希望自己造,所以今天我们自己来动手撸一个日志库,文章重点讲实现过程,如果需要源码,可以前往github获取FdogLog,一个轻量级C++日志库,用于日志服务。
跪求三连!
二. 基本功能
我们先来捋一捋这个日志库应该实现那些功能。
- 日志最最最基本的功能是什么 20000 f0c;当然是打印或记录日志。
- 信息应该包括哪些信息,时间?运行用户?所在文件?想要显示的信息?(自定义显示信息下篇实现)
- 信息虽然显示丰富,但是要尽可能让代码自己获取其他信息,调用者只需要设置最主要的信息。
- 信息有重要等级之分,所以我们需要对信息做必要分类,提高效率。
- 如何实现全局尽可能简洁的调用。
- 如果日志库是运行在多线程环境,如何保证线程安全。(下篇实现)
这些就是一个日志库所具备的最基本的功能,接下来继续思考,还需要什么。
- 怎么控制日志的行为。
- 如果保存在文件,如何定义文件名。
- 随着日志增加,文件会越来越大,如何解决。(下篇实现)
简单规划完一个不那么完美的日志库所具备的能力,现在我们来对这几条做更详细的规划。
- 日志最最最基本的功能是什么,当然是打印或记录日志。
- 信息应该包括哪些信息,时间?运行用户?所在文件?想要显示的信息?
当我在调用一个名为function的函数时。
function();
你希望它输出怎么样的信息。
我被调用
[2021-10-20 23:27:23] 我被调用
[2021-10-20 23:27:23] INFO 我被调用
[2021-10-20 23:27:23] INFO root 我被调用
[2021-10-20 23:27:23] INFO root 17938 我被调用
[2021-10-20 23:27:23] INFO root 17938 [/media/rcl/FdogIM/service.h function:8] 我被调用
我想大部分人都会选择最后一种输出信息吧(虽然在这之前,我们都大量使用cout输出第一种),所以我们的日志应该包括时间,日志等级,运行用户,进程ID,调用函数所在文件,以及调用时所在行数。当然总会有人不想全部输出,这将在后面给出方案。
-
信息虽然显示丰富,但是要尽可能让代码自己获取其他信息,调用者只需要设置最主要的信息。
-
信息有重要等级之分,所以我们需要对信息做必要分类,提高效率。
-
如何实现全局尽可能简洁的调用.
信息有重要等级之分,要可以对信息做区分,按照常见的等级之分,有:
ERROR: 此信息输出后,主体系统核心模块不能正常工作,需要修复才能正常工作。
WARN: 此信息输出后,系统一般模块存在问题,不影响系统运行。
INFO: 此信息输出后,主要是记录系统运行状态等关联信息。
DEBUG: 最细粒度的输出,除去上面各种情况后,你希望输出的相关信息,都可以在这里输出。
TRACE: 最细粒度的输出,除去上面各种情况后,你希望输出的相关信息,都可以在这里输出。
有了等级之分,如何实现全局尽可能简洁的调用,通俗的说就是去掉一切不必要的调用,只留下最主要的调用。
例如:
#include<iostream> #include"fdoglogger.h" //添加日志库头文件 using namespace fdog; //日志库的命名空间 int main(){ FdogError("错误"); FdogWarn("警告"); FdogInfo("信息"); FdogDebug("调试"); FdogTrace("追踪"); return 0; }
你不必初始化什么信息,调用什么多余的初始化函数,只需要用这五个类似函数的东西来输出即可,同样,如果是另一个源文件,依旧是这样的调用方式(这里可以使用单一模式来实现,其意图是保证一个类仅有一个实列,并提供一个访问它的全局访问点,该实例被所有程序模块共享。就比如日志的输出。)。
- 如果日志库是运行在多线程环境,如何保证线程安全。
到目前,一个基本的日志库的调用基本成形,如果在单线程,它可以很好的工作,但是到了多线程环境下,就不能保证了,第一点就是单例模式的创建,当两个线程同时去初始化时,无法保证单一实例被成功创建,第二,日志既然是输出到文件,不同线程写入文件时,如何保证写入数据不会错乱。既然写的是C++的日志输出,必然用到了cout ,cout 不是原子性的操作,所以在多线程下是不安全的,这些都是我们需要考虑到的。
- 怎么控制日志的行为。
这里使用配置文件进行日志的行为规定,包括打印什么日志,输入到文件,还是终端,输出的等级,以及日志开关,等等,配置文件将在程序启动时被读取。(提醒各位千万不要写死代码,后患无穷!!!)
-
如果保存在文件,如何定义文件名。
-
随着日志增加,文件会越来越大,如何解决。
日志的文件名由配置文件指定,但是创建时会在后面加上创建日期后缀,并且可以在配置文件中配置每隔多少天创建一个新的日志文件,如果配置中心有设置日志文件大小,则会优先大小判断,超过便创建一个新文件。
三. 代码实现
1. fdoglogger.h
#ifndef FDOGLOGGER_H #define FDOGLOGGER_H #include<iostream> #include<fstream> #include<map> #include<mutex> #ifndef linux #include<unistd.h> #include<sys/syscall.h> #include<sys/stat.h> #include<sys/types.h> #include <pwd.h> #endif #ifndef WIN32 //TODO #endif using namespace std; namespace fdog { #define RED "\e[1;31m" #define BLUE "\e[1;34m" #define GREEN "\e[1;32m" #define WHITE "\e[1;37m" #define DEFA "\e[0m" enum class coutType: int {Error, Warn, Info, Debug, Trace}; enum class fileType: int {Error, Warn, Info, Debug, Trace}; enum class terminalType: int {Error, Warn, Info, Debug, Trace}; struct Logger { string logSwitch; //日志开关 string logFileSwitch; //是否写入文件 string logTerminalSwitch; //是否打印到终端 string logName; //日志文件名字 string logFilePath; //日志文件保存路径 string logMixSize; //日志文件最大大小 string logBehavior; //日志文件达到最大大小行为 string logOverlay; //日志文件覆盖时间 string logOutputLevelFile; //日志输出等级(file) string logOutputLevelTerminal;//日志输出等级 }; class FdogLogger { public: void initLogConfig(); void releaseConfig(); static FdogLogger* getInstance(); string getCoutType(coutType coutType); bool getFileType(fileType fileCoutBool); bool getTerminalType(terminalType terminalCoutTyle); string getLogCoutTime(); string getLogNameTime(); string getFilePash(); string getLogCoutProcessId(); string getLogCoutThreadId(); string getLogCoutUserName(); bool createFile(string filePash); bool logFileWrite(string messages); bool bindFileCoutMap(string value1, fileType value2); bool bindTerminalCoutMap(string value1, terminalType value2); private: char szbuf[128]; Logger logger; static FdogLogger * singleObject; static mutex * mutex_new; map<coutType, string> coutTypeMap; map<fileType, bool> fileCoutMap; map<terminalType, bool> terminalCoutMap; private: FdogLogger(); ~FdogLogger(); }; #define Error1 __FDOGNAME__(Error) #define Warn1 __FDOGNAME__(Warn) #define Info1 __FDOGNAME__(Info) #define Debug1 __FDOGNAME__(Debug) #define Trace1 __FDOGNAME__(Trace) #define SQUARE_BRACKETS_LEFT " [" #define SQUARE_BRACKETS_RIGHT "] " #define SPACE " " #define LINE_FEED "\n" #define COLON ":" #define SLASH "/" #define __FDOGTIME__ FdogLogger::getInstance()->getLogCoutTime() //时间宏 #define __FDOGPID__ FdogLogger::getInstance()->getLogCoutProcessId() //进程宏 #define __FDOGTID__ FdogLogger::getInstance()->getLogCoutThreadId() //线程宏 #define __FDOGFILE__ __FILE__ //文件名宏 #define __FDOGPASH__ FdogLogger::getInstance()->getFilePash() + __FDOGFILE__ //文件路径 #define __FDOGFUNC__ __func__ //函数名宏 #define __FDOGLINE__ __LINE__ //行数宏 #define __USERNAME__ FdogLogger::getInstance()->getLogCoutUserName() //获取调用用户名字 #define __FDOGNAME__(name) #name //名字宏 #define COMBINATION_INFO_FILE(coutTypeInfo, message) \ do{\ string messagesAll = __FDOGTIME__ + coutTypeInfo + __USERNAME__ + __FDOGTID__ + SQUARE_BRACKETS_LEFT + \ __FDOGPASH__ + SPACE +__FDOGFUNC__ + COLON + to_string(__FDOGLINE__) + SQUARE_BRACKETS_RIGHT + message + LINE_FEED;\ FdogLogger::getInstance()->logFileWrite(messagesAll); \ }while(0); #define COMBINATION_INFO_TERMINAL(coutTypeInfo, message) \ do{\ string messagesAll = __FDOGTIME__ + WHITE + coutTypeInfo + DEFA + __USERNAME__ + __FDOGTID__ + SQUARE_BRACKETS_LEFT + \ __FDOGPASH__ + SPACE +__FDOGFUNC__ + COLON + to_string(__FDOGLINE__) + SQUARE_BRACKETS_RIGHT + message + LINE_FEED;\ cout << messagesAll;\ }while(0); #define LoggerCout(coutTyle, coutTypeInfo, fileCoutBool, terminalCoutBool, message) \ do {\ string coutType = FdogLogger::getInstance()->getCoutType(coutTyle);\ if (FdogLogger::getInstance()->getFileType(fileCoutBool)) {\ COMBINATION_INFO_FILE(coutTypeInfo, message)\ }\ if (FdogLogger::getInstance()->getTerminalType(terminalCoutBool)) {\ COMBINATION_INFO_TERMINAL(coutTypeInfo, message)\ }\ }while(0); #define FdogError(message) \ do{\ LoggerCout(fdog::coutType::Error, Error1, fdog::fileType::Error, fdog::terminalType::Error, message)\ }while(0); #define FdogWarn(message) \ do{\ LoggerCout(fdog::coutType::Warn, Warn1, fdog::fileType::Warn, fdog::terminalType::Warn, message)\ }while(0); #define FdogInfo(message) \ do{\ LoggerCout(fdog::coutType::Info, Info1, fdog::fileType::Info, fdog::terminalType::Info, message)\ }while(0); #define FdogDebug(message) \ do{\ LoggerCout(fdog::coutType::Debug, Debug1, fdog::fileType::Debug, fdog::terminalType::Debug, message)\ }while(0); #define FdogTrace(message) \ do{\ LoggerCout(fdog::coutType::Trace, Trace1, fdog::fileType::Trace, fdog::terminalType::Trace, message)\ }while(0); } #endif
2. fdoglogger.cpp
#include"fdoglogger.h" using namespace fdog; FdogLogger * FdogLogger::singleObject = nullptr; mutex * FdogLogger::mutex_new = new(mutex); FdogLogger::FdogLogger(){ initLogConfig(); } FdogLogger::~FdogLogger(){ } FdogLogger* FdogLogger::getInstance(){ mutex_new->lock(); if (singleObject == nullptr) { singleObject = new FdogLogger(); } mutex_new->unlock(); return singleObject; } void FdogLogger::initLogConfig(){ map<string, string *> flogConfInfo; flogConfInfo["logSwitch"] = &this->logger.logSwitch; flogConfInfo["logFileSwitch"] = &this->logger.logFileSwitch; flogConfInfo["logTerminalSwitch"] = &this->logger.logTerminalSwitch; flogConfInfo["logName"] = &this->logger.logName; flogConfInfo["logFilePath"] = &this->logger.logFilePath; flogConfInfo["logMixSize"] = &this->logger.logMixSize; flogConfInfo["logBehavior"] = &this->logger.logBehavior; flogConfInfo["logOverlay"] = &this->logger.logOverlay; flogConfInfo["logOutputLevelFile"] = &this->logger.logOutputLevelFile; flogConfInfo["logOutputLevelTerminal"] = &this->logger.logOutputLevelTerminal; string str; ifstream file; char str_c[100]={0}; file.open("fdoglogconf.conf"); if(!file.is_open()){ cout<<"文件打开失败\n"; } while(getline(file, str)){ if(!str.length()) { continue; } string str_copy = str; //cout<<"获取数据:"<<str_copy<<endl; int j = 0; for(int i = 0; i < str.length(); i++){ if(str[i]==' ')continue; str_copy[j] = str[i]; j++; } str_copy.erase(j); if(str_copy[0]!='#'){ sscanf(str_copy.data(),"%[^=]",str_c); auto iter = flogConfInfo.find(str_c); if(iter!=flogConfInfo.end()){ sscanf(str_copy.data(),"%*[^=]=%s",str_c); *iter->second = str_c; } else { } } } logger.logName = logger.logName + getLogNameTime() + ".log"; bindFileCoutMap("5", fileType::Error); bindFileCoutMap("4", fileType::Warn); bindFileCoutMap("3", fileType::Info); bindFileCoutMap("2", fileType::Debug); bindFileCoutMap("1", fileType::Trace); bindTerminalCoutMap("5", terminalType::Error); bindTerminalCoutMap("4", terminalType::Warn); bindTerminalCoutMap("3", terminalType::Info); bindTerminalCoutMap("2", terminalType::Debug); bindTerminalCoutMap("1", terminalType::Trace); if(logger.logFileSwitch == "on"){ if(!createFile(logger.logFilePath)){ std::cout<<"Log work path creation failed\n"; } } cout << "|========FdogLogger v2.0==========================|" <<endl << endl; cout << " 日志开关:" << logger.logSwitch << endl; cout << " 文件输出:" << logger.logFileSwitch << endl; cout << " 终端输出:" << logger.logTerminalSwitch << endl; cout << " 日志输出等级(文件):" << logger.logOutputLevelFile << endl; cout << " 日志输出等级(终端):" << logger.logOutputLevelTerminal << endl; cout << " 日志文件名:" << logger.logName << endl; cout << " 日志保存路径:" << logger.logFilePath << endl; cout << " 单文件最大大小:"<< logger.logMixSize << "M" << endl; cout << " 日志保存时间 :" << logger.logOverlay << "天" << endl << endl; cout << "|=================================================|" <<endl; return; } string FdogLogger::getCoutType(coutType coutType){ return singleObject->coutTypeMap[coutType]; } bool FdogLogger::getFileType(fileType fileCoutBool){ return singleObject->fileCoutMap[fileCoutBool]; } bool FdogLogger::getTerminalType(terminalType terminalCoutTyle){ return singleObject->terminalCoutMap[terminalCoutTyle]; } string FdogLogger::getLogCoutTime(){ time_t timep; time (&timep); char tmp[64]; strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S",localtime(&timep)); string tmp_str = tmp; return SQUARE_BRACKETS_LEFT + tmp_str + SQUARE_BRACKETS_RIGHT; } string FdogLogger::getLogNameTime(){ time_t timep; time (&timep); char tmp[64]; strftime(tmp, sizeof(tmp), "%Y-%m-%d-%H:%M:%S",localtime(&timep)); return tmp; } string FdogLogger::getFilePash(){ getcwd(szbuf, sizeof(szbuf)-1); string szbuf_str = szbuf; return szbuf_str + SLASH; } string FdogLogger::getLogCoutProcessId(){ #ifndef linux return to_string(getpid()); #endif #ifndef WIN32 // unsigned long GetPid(){ // return GetCurrentProcessId(); // } #endif } string FdogLogger::getLogCoutThreadId(){ #ifndef linux return to_string(syscall(__NR_gettid)); #endif #ifndef WIN32 // unsigned long GetTid(){ // return GetCurrentThreadId(); // } #endif } string FdogLogger::getLogCoutUserName(){ struct passwd *my_info; my_info = getpwuid(getuid()); string name = my_info->pw_name; return SPACE + name + SPACE; } bool FdogLogger::createFile(string filePash){ int len = filePash.length(); if(!len){ filePash = "log"; if (0 != access(filePash.c_str(), 0)){ if(-1 == mkdir(filePash.c_str(),0)){ std::cout<<"没路径"; return 0; } } } std::string filePash_cy(len,'\0'); for(int i =0;i<len;i++){ filePash_cy[i]=filePash[i]; if(filePash_cy[i]=='/' || filePash_cy[i]=='\\'){ if (-1 == access(filePash_cy.c_str(), 0)){ if(0!=mkdir(filePash_cy.c_str(),0)){ std::cout<<"有路径"; return 0; } } } } return 1; } bool FdogLogger::logFileWrite(string messages){ ofstream file; file.open(logger.logFilePath + logger.logName, ::ios::app | ios::out); if(!file){ cout<<"写失败"<<endl; return 0; } file << messages; file.close(); return 1; } bool FdogLogger::bindFileCoutMap(string value1, fileType value2){ if(logger.logOutputLevelFile.find(value1)!=std::string::npos) { fileCoutMap[value2] = true; } else { fileCoutMap[value2] = false; } } bool FdogLogger::bindTerminalCoutMap(string value1, terminalType value2){ if(logger.logOutputLevelTerminal.find(value1)!=std::string::npos) { terminalCoutMap[value2] = true; } else { terminalCoutMap[value2] = false; } }
四. 测试用例
1. fdoglogger_test.cpp
#include<iostream> #include"fdoglogger.h" //添加日志库头文件 using namespace fdog; //日志库的命名空间 int main(){ FdogError("错误"); FdogWarn("警告"); FdogInfo("信息"); FdogDebug("调试"); FdogTrace("追踪"); return 0; }
暂时考虑到的就是这些,如有缺陷,欢迎评论区补充。(比如文件写入打开就关闭,很浪费资源,如何优化,下篇见)。
源码已上传github,还原star! FdogLog,一个轻量级C++日志库,用于日志服务。
- 【深度学习】笔记7: CNN训练Cifar-10技巧 ---如何进行实验,如何进行构建自己的网络模型,提高精度
- C/C++ 日常学习总结(第二十篇)实现自己的printf函数
- UE4 学习笔记(1)——如何用VS进行UE4的Debug
- play framework如何进行模块化开发--学习笔记(借鉴同事、博客等资料自己试验通过!)
- QT中如何进行DEBUG和使用cout,cin等
- .Net语言 APP开发平台——Smobiler学习日志:如何调用API进行短信发送
- 【从零开始的c/c++学习之旅】如何使用return来进行返回值的输出
- 请博友们帮助一下,为什么自己的博客找不到 搜索功能呀,我的博文多了,如何进行搜索呀
- QT中如何进行DEBUG和使用cout,cin等
- 深度学习应用系列(二) | 如何使用keras进行迁移学习,以训练和识别自己的图片集
- linux系统异常,学习如何通过系统日志进行初步排查
- QT中如何进行DEBUG和使用cout,cin等
- cocos2d-x 学习日志(1)之Xcode中c++&Object-C混编,详细介绍如何在cocos2dx中访问object函数以及Apple Api
- 如何在xcode下面同时安装cocos2d-iphone 和 cocos2d-x模板,其实是因为很喜欢C++的缘故,当时学习的是前者,现在自己摸着石头过河了就(cocos2d-x安装失败 出错)
- play framework如何进行模块化开发--学习笔记(借鉴同事、博客等资料自己试验通过!)
- 在PCH中定制自己的LOG打印日志,分别在DEBUG 与 RELEASE的状态下处理,及如何把PCH引入到项目中
- 如何在C++中使用cout进行高级的格式化输出操作
- 菜鸟如何学习用C++和C#语言在VS进行编程开发?
- C++学习之cout如何控制输出数字的精度
- C++/GUI/Qt学习——如何在Console中使用cin、cout、cerr