C++还在用printf/cout进行Debug?学习一下如何自己写日志库吧(上篇)
Posted 花狗Fdog
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++还在用printf/cout进行Debug?学习一下如何自己写日志库吧(上篇)相关的知识,希望对你有一定的参考价值。
文章目录
一. 前言
哈喽,自从实习以来很久没有更文了,一是没有时间,二是实习了之后突然发现自己能写的东西也没有多少了。赶上1024有征文活动,就写一篇吧,在实习的这段时间,我更加认识到日志的重要性,客户端值没传过来?看日志,服务崩溃了?看日志,没错,日志是出现异常第一个想到的东西,它记录了程序运行过程中所调用的函数,所接受到的值,所执行的行为等等。大家也都看到这篇的标题了,我这个人有一个缺点,就是不太喜欢用别人的东西,如果有能力,我希望自己造,所以今天我们自己来动手撸一个日志库,文章重点讲实现过程,如果需要源码,可以前往github获取FdogLog,一个轻量级C++日志库,用于日志服务。
跪求三连!
二. 基本功能
我们先来捋一捋这个日志库应该实现那些功能。
- 日志最最最基本的功能是什么,当然是打印或记录日志。
- 信息应该包括哪些信息,时间?运行用户?所在文件?想要显示的信息?(自定义显示信息下篇实现)
- 信息虽然显示丰富,但是要尽可能让代码自己获取其他信息,调用者只需要设置最主要的信息。
- 信息有重要等级之分,所以我们需要对信息做必要分类,提高效率。
- 如何实现全局尽可能简洁的调用。
- 如果日志库是运行在多线程环境,如何保证线程安全。(下篇实现)
这些就是一个日志库所具备的最基本的功能,接下来继续思考,还需要什么。
- 怎么控制日志的行为。
- 如果保存在文件,如何定义文件名。
- 随着日志增加,文件会越来越大,如何解决。(下篇实现)
简单规划完一个不那么完美的日志库所具备的能力,现在我们来对这几条做更详细的规划。
- 日志最最最基本的功能是什么,当然是打印或记录日志。
- 信息应该包括哪些信息,时间?运行用户?所在文件?想要显示的信息?
当我在调用一个名为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 == 以上是关于C++还在用printf/cout进行Debug?学习一下如何自己写日志库吧(上篇)的主要内容,如果未能解决你的问题,请参考以下文章
C++还在用printf/cout进行Debug?学习一下如何自己写日志库吧(上篇)