AI智能
改变未来

C++还在用printf/cout进行Debug?学习一下如何自己写日志库吧(上篇)

文章目录

  • 一. 前言
  • 二. 基本功能
  • 三. 代码实现
  • 1. fdoglogger.h
  • 2. fdoglogger.cpp
  • 四. 测试用例
    • 1. fdoglogger_test.cpp

    一. 前言

    哈喽,自从实习以来很久没有更文了,一是没有时间,二是实习了之后突然发现自己能写的东西也没有多少了。赶上1024有征文活动,就写一篇吧,在实习的这段时间,我更加认识到日志的重要性,客户端值没传过来?看日志,服务崩溃了?看日志,没错,日志是出现异常第一个想到的东西,它记录了程序运行过程中所调用的函数,所接受到的值,所执行的行为等等。大家也都看到这篇的标题了,我这个人有一个缺点,就是不太喜欢用别人的东西,如果有能力,我希望自己造,所以今天我们自己来动手撸一个日志库,文章重点讲实现过程,如果需要源码,可以前往github获取FdogLog,一个轻量级C++日志库,用于日志服务。

    跪求三连!

    二. 基本功能

    我们先来捋一捋这个日志库应该实现那些功能。

    1. 日志最最最基本的功能是什么�当然是打印或记录日志。
    2. 信息应该包括哪些信息,时间?运行用户?所在文件?想要显示的信息?(自定义显示信息下篇实现)
    3. 信息虽然显示丰富,但是要尽可能让代码自己获取其他信息,调用者只需要设置最主要的信息。
    4. 信息有重要等级之分,所以我们需要对信息做必要分类,提高效率。
    5. 如何实现全局尽可能简洁的调用。
    6. 如果日志库是运行在多线程环境,如何保证线程安全。(下篇实现)

    这些就是一个日志库所具备的最基本的功能,接下来继续思考,还需要什么。

    1. 怎么控制日志的行为。
    2. 如果保存在文件,如何定义文件名。
    3. 随着日志增加,文件会越来越大,如何解决。(下篇实现)

    简单规划完一个不那么完美的日志库所具备的能力,现在我们来对这几条做更详细的规划。

    1. 日志最最最基本的功能是什么,当然是打印或记录日志。
    2. 信息应该包括哪些信息,时间?运行用户?所在文件?想要显示的信息?

    当我在调用一个名为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,调用函数所在文件,以及调用时所在行数。当然总会有人不想全部输出,这将在后面给出方案。

    1. 信息虽然显示丰富,但是要尽可能让代码自己获取其他信息,调用者只需要设置最主要的信息。

    2. 信息有重要等级之分,所以我们需要对信息做必要分类,提高效率。

    3. 如何实现全局尽可能简洁的调用.

    信息有重要等级之分,要可以对信息做区分,按照常见的等级之分,有:

    ERROR: 此信息输出后,主体系统核心模块不能正常工作,需要修复才能正常工作。
    WARN:   此信息输出后,系统一般模块存在问题,不影响系统运行。
    INFO:     此信息输出后,主要是记录系统运行状态等关联信息。
    DEBUG: 最细粒度的输出,除去上面各种情况后,你希望输出的相关信息,都可以在这里输出。
    TRACE:  最细粒度的输出,除去上面各种情况后,你希望输出的相关信息,都可以在这里输出。

    有了等级之分,如何实现全局尽可能简洁的调用,通俗的说就是去掉一切不必要的调用,只留下最主要的调用。

    例如:

    #include<iostream>#include"fdoglogger.h"  //添加日志库头文件using namespace fdog;   //日志库的命名空间int main(){FdogError("错误");FdogWarn("警告");FdogInfo("信息");FdogDebug("调试");FdogTrace("追踪");return 0;}

    你不必初始化什么信息,调用什么多余的初始化函数,只需要用这五个类似函数的东西来输出即可,同样,如果是另一个源文件,依旧是这样的调用方式(这里可以使用单一模式来实现,其意图是保证一个类仅有一个实列,并提供一个访问它的全局访问点,该实例被所有程序模块共享。就比如日志的输出。)。

    1. 如果日志库是运行在多线程环境,如何保证线程安全。

    到目前,一个基本的日志库的调用基本成形,如果在单线程,它可以很好的工作,但是到了多线程环境下,就不能保证了,第一点就是单例模式的创建,当两个线程同时去初始化时,无法保证单一实例被成功创建,第二,日志既然是输出到文件,不同线程写入文件时,如何保证写入数据不会错乱。既然写的是C++的日志输出,必然用到了cout ,cout 不是原子性的操作,所以在多线程下是不安全的,这些都是我们需要考虑到的。

    1. 怎么控制日志的行为。

    这里使用配置文件进行日志的行为规定,包括打印什么日志,输入到文件,还是终端,输出的等级,以及日志开关,等等,配置文件将在程序启动时被读取。(提醒各位千万不要写死代码,后患无穷!!!)

    1. 如果保存在文件,如何定义文件名。

    2. 随着日志增加,文件会越来越大,如何解决。

    日志的文件名由配置文件指定,但是创建时会在后面加上创建日期后缀,并且可以在配置文件中配置每隔多少天创建一个新的日志文件,如果配置中心有设置日志文件大小,则会优先大小判断,超过便创建一个新文件。

    三. 代码实现

    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#endifusing 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 linuxreturn to_string(getpid());#endif#ifndef WIN32//  unsigned long GetPid(){//     return GetCurrentProcessId();// }#endif}string FdogLogger::getLogCoutThreadId(){#ifndef linuxreturn 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++日志库,用于日志服务。

  • 赞(0) 打赏
    未经允许不得转载:爱站程序员基地 » C++还在用printf/cout进行Debug?学习一下如何自己写日志库吧(上篇)