C++实现的一个简单的日志库

xiaoxiao2021-02-28  106

最近一直在看《Linux多线程服务器编程》这本书。看了里面实现的一个日志库功能,觉得很有意思。于是,就在2周前就准备开始模仿它实现。由于最近比较忙,直到今天才完成,其实也没怎么去验证和测试。毕竟只是作为自己学习之用,来检验是否真看懂了原先的实现。

就目前来看功能算是能用,目前也没有发现什么问题。就先这样,以后有时间的话在去重新设计。

整个日志库相对比较简单,目前只支持2个设置。设置日志等级和输出模式。

日志等级共支持5种等级:

DEBUG Level:指出细粒度信息事件对调试应用程序是非常有帮助的。 INFO Level:消息在粗粒度级别上突出强调应用程序的运行 WARN Level:表明会出现潜在错误的情况。 ERROR Level:指出虽然发生错误事件,但仍然不影响系统的继续运行。 FATAL Level:指出每个严重的错误事件将会导致应用程序的退出。日志输出模式共支持3种输出:

//这几个宏是用来设置输出模式的:输出到标准输出 输出到log文件 输出到标准输出和log文件 #define LOGGER_MODE_STDOUT 0x01 #define LOGGER_MODE_LOGFILE 0x02 #define LOGGER_MODE_OUTANDFILE (LOGGER_MODE_STDOUT | LOGGER_MODE_LOGFILE)事实上一个完整的日志库会有许多设置供用户选择。具体可以参考一些开源的日志库。

目前由于时间有限,没有继续的添加功能。

好了,废话不多说了,先来看看效果吧!

#include <iostream> #include "Logger.h" using namespace std; using namespace Log; //现在通过一个测试用例来看看怎么使用及效果怎么样 int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mult(int a, int b) { return a * b; } int divide(int a, int b) { if (b == 0) LOG_FATAL << "Divisor cannot be zero"; return a / b; } int main(void) { //日志的报警等级为INFO CLogger::setLogLevel(CLogger::INFO); //我们设置日志输出到文件和标准输出 CLogger::setOutputMode(LOGGER_MODE_OUTANDFILE); LOG_INFO << "The test program began to run!"; LOG_DEBUG << "Debug Mode!"; int iNum1; int iNum2; int iSum; cout << "Please inout two number:" << endl; cin >> iNum1 >> iNum2; LOG_INFO << "User input two number: " << iNum1 << "and" << iNum2; iSum = add(iNum1, iNum2); LOG_INFO << "iNum1 + iNum2 = " << iSum; iSum = sub(iNum1, iNum2); LOG_INFO << "iNum1 - iNum2 = " << iSum; iSum = mult(iNum1, iNum2); LOG_INFO << "iNum1 * iNum2 = " << iSum; iSum = divide(iNum1, iNum2); LOG_INFO << "iNum1 / iNum2 = " << iSum; LOG_INFO << "The test program end!"; return 0; }上面的测试代码很简单,就是输入2个数,计算加,减,乘,除。我们用这个日志库来记录起来。

我们可以测试下当除数为0时的情况。

和预想的一样,当发生发生等级为FATAL错误时结束程序。

测试分析就到此结束,这个日志库分别在VS2013上和Ubuntu上运行通过,Ubuntu上需要定义LINUX。

下面就贴上代码的实现。代码中有比较详细的注释。

1.LogCommon.h

#ifndef __LOG_COMMON_H__ #define __LOG_COMMON_H__ #include <string.h> namespace Log { typedef struct LogDate { int m_iYear; int m_iMon; int m_iDay; }LOGDATE; //下面这个类主要是将__FILE__(包含文件完整路径和文件名)转化为对应的源文件 //事实上日志中为了简洁去掉完整的路径,只保留文件名 class CFileName { public: CFileName(const char *pFilePathAndName) :m_pFileName(pFilePathAndName) , m_iLen(static_cast<int>(strlen(pFilePathAndName))) { const char *pTmp = #ifdef LINUX strrchr(pFilePathAndName, '/'); #else strrchr(pFilePathAndName, '\\'); #endif //#ifdef LINUX if (pTmp != NULL) { m_pFileName = pTmp + 1; m_iLen = static_cast<int>(strlen(m_pFileName)); } } const char *m_pFileName; int m_iLen; }; //下面这个类用来作为一个固定缓存,这个缓存将输出的日志信息 //追加到缓存区中,以便最后一条完整日志的输出。 const int iFixedBuffer = 4096; //可以使用的正常的缓存大小 const int iFixedBufferBig = 65535; //可以使用的最大的缓存大小 template<int SIZE> class CFixedBuffer { public: CFixedBuffer() :m_pCur(m_arrData) { memset(m_arrData, 0x00, SIZE); } ~CFixedBuffer() { //nothing } void append(const char *pData, int iLen) { char *pEndBuffer = &m_arrData[SIZE - 1]; if (static_cast<int>(pEndBuffer - m_pCur) > iLen) { memcpy(m_pCur, pData, iLen); m_pCur += iLen; } } //这个方法是用来获取buffer数据 const char *data() { return m_arrData; } //这个方法是用来获取buffer的长度 int len() { return static_cast<int>(m_pCur - m_arrData); } //这个方法是用来将某字符串追加到buffer中 void appendString(const char *pString, int iLen) { memcpy(m_pCur, pString, iLen); m_pCur += iLen; } //这个方法是用来返回buffer当前的指针位置, //以便外部能直接操作buffer,提高效率 char *getCurrent(void) { return m_pCur; } //这个方法与getCurrent配套使用 void appendLen(int iLen) { m_pCur += iLen; } private: char m_arrData[SIZE]; char *m_pCur; }; //下面这个函数模板是用来实现整型转换为字符串 const char NumTab[] = { "9876543210123456789" }; template<typename T> int NumberToString(char arrBuffer[], T value) { int iStrNum = 0; int iRemainder; T TmpValue1 = value; T TmpValue2 = value; const char *pNumTab = &NumTab[9]; if (value < 0) iStrNum++; do { iStrNum++; } while ((TmpValue1 /= 10) != 0); char *pTmp = arrBuffer + iStrNum; *pTmp-- = '\0'; do { iRemainder = static_cast<int>(TmpValue2 % 10); *pTmp-- = pNumTab[iRemainder]; TmpValue2 /= 10; } while (TmpValue2 != 0); if (value < 0) *pTmp = '-'; return iStrNum; } } #endif //#ifndef __LOG_COMMON_H__ 2.LogTime.h

#ifndef __LOG_TIME_H__ #define __LOG_TIME_H__ #include <string> namespace Log { class CLogTime { public: CLogTime(); ~CLogTime(); std::string &getLogTime(); private: std::string m_strTimeString; }; } #endif //#ifndef __LOG_TIME_H__ 3.LogTime.cpp

#include <time.h> #include "LogTime.h" #include "LogFile.h" #include "LogCommon.h" #if _MSC_VER #define snprintf _snprintf #endif namespace Log { static const char *arrWeek[] = { "Sunday", "Monday", "Tuesday", "Wednessday","Thursday", "Friday", "Saturday" }; CLogTime::CLogTime() { char arrBuffer[32]; memset(arrBuffer, 0x00, sizeof(arrBuffer)); time_t timep; struct tm *pTm; time(&timep); pTm = localtime(&timep); snprintf(arrBuffer, sizeof(arrBuffer), "%d-d-d d:d:d %s ", pTm->tm_year + 1900, pTm->tm_mon + 1, pTm->tm_mday, pTm->tm_hour, pTm->tm_min, pTm->tm_sec, arrWeek[pTm->tm_wday]); m_strTimeString = arrBuffer; LOGDATE tDate; tDate.m_iYear = pTm->tm_year + 1900; tDate.m_iMon = pTm->tm_mon + 1; tDate.m_iDay = pTm->tm_mday; CLogFile *pLogFile = CLogFile::instance(); pLogFile->updateLogFile(&tDate); } CLogTime::~CLogTime() { //nothing } std::string &CLogTime::getLogTime() { return m_strTimeString; } } 4.LogStream.h

#ifndef __LOG_STREAM_H__ #define __LOG_STREAM_H__ #include <string> #include "LogCommon.h" namespace Log { //这个类主要是日志流的实现 //其实就是重载了操作符<< //可能没有考虑到所有情况,就先这样吧 class CLogStream { public: CLogStream(); ~CLogStream(); const char *GetStreamBuff(void); int GetStreamBuffLen(void); CLogStream &operator<<(char); CLogStream &operator<<(unsigned char); CLogStream &operator<<(short); CLogStream &operator<<(unsigned short); CLogStream &operator<<(int); CLogStream &operator<<(unsigned int); CLogStream &operator<<(long); CLogStream &operator<<(unsigned long); CLogStream &operator<<(long long); CLogStream &operator<<(unsigned long long); CLogStream &operator<<(float); CLogStream &operator<<(double); CLogStream &operator<<(char *pChar); CLogStream &operator<<(const char *pChar); CLogStream &operator<<(unsigned char *pChar); CLogStream &operator<<(const unsigned char *pChar); CLogStream &operator<<(std::string strString); private: typedef CFixedBuffer<iFixedBuffer> FixedBuffer; FixedBuffer m_Buffer; //这个就是具体的Buffer }; } #endif //#ifndef __LOG_STREAM_H__ 5.LogStream.cpp

#include <stdio.h> #include "LogStream.h" #if _MSC_VER #define snprintf _snprintf #endif namespace Log { const int iFloatMaxStrLen = 16; const int iDoubleMaxStrLen = 32; CLogStream::CLogStream() { } CLogStream::~CLogStream() { } const char *CLogStream::GetStreamBuff(void) { return m_Buffer.data(); } int CLogStream::GetStreamBuffLen(void) { return m_Buffer.len(); } CLogStream &CLogStream::operator << (char value) { int iLen = NumberToString(m_Buffer.getCurrent(), value); m_Buffer.appendLen(iLen); return *this; } CLogStream &CLogStream::operator << (unsigned char value) { int iLen = NumberToString(m_Buffer.getCurrent(), value); m_Buffer.appendLen(iLen); return *this; } CLogStream &CLogStream::operator << (short value) { int iLen = NumberToString(m_Buffer.getCurrent(), value); m_Buffer.appendLen(iLen); return *this; } CLogStream &CLogStream::operator << (unsigned short value) { int iLen = NumberToString(m_Buffer.getCurrent(), value); m_Buffer.appendLen(iLen); return *this; } CLogStream &CLogStream::operator << (int value) { int iLen = NumberToString(m_Buffer.getCurrent(), value); m_Buffer.appendLen(iLen); return *this; } CLogStream &CLogStream::operator << (unsigned int value) { int iLen = NumberToString(m_Buffer.getCurrent(), value); m_Buffer.appendLen(iLen); return *this; } CLogStream &CLogStream::operator << (long value) { int iLen = NumberToString(m_Buffer.getCurrent(), value); m_Buffer.appendLen(iLen); return *this; } CLogStream &CLogStream::operator << (unsigned long value) { int iLen = NumberToString(m_Buffer.getCurrent(), value); m_Buffer.appendLen(iLen); return *this; } CLogStream &CLogStream::operator << (long long value) { int iLen = NumberToString(m_Buffer.getCurrent(), value); m_Buffer.appendLen(iLen); return *this; } CLogStream &CLogStream::operator << (unsigned long long value) { int iLen = NumberToString(m_Buffer.getCurrent(), value); m_Buffer.appendLen(iLen); return *this; } CLogStream &CLogStream::operator<<(float fValue) { int iLen = snprintf(m_Buffer.getCurrent(), iFloatMaxStrLen, "%f", fValue); m_Buffer.appendLen(iLen); return *this; } CLogStream &CLogStream::operator<<(double dValue) { int iLen = snprintf(m_Buffer.getCurrent(), iDoubleMaxStrLen, "%lf", dValue); m_Buffer.appendLen(iLen); return *this; } CLogStream &CLogStream::operator<<(char *pChar) { m_Buffer.appendString(pChar, strlen(pChar)); return *this; } CLogStream &CLogStream::operator<<(const char *pChar) { m_Buffer.appendString(pChar, strlen(pChar)); return *this; } CLogStream &CLogStream::operator<<(unsigned char *pChar) { return operator<<(reinterpret_cast<char *>(pChar)); } CLogStream &CLogStream::operator<<(const unsigned char *pChar) { return operator<<(reinterpret_cast<const char *>(pChar)); } CLogStream &CLogStream::operator << (std::string strString) { return operator<<(strString.c_str()); } } 6.LogFile.h

#ifndef __LOG_FILE_H__ #define __LOG_FILE_H__ #include <fstream> #include "LogCommon.h" namespace Log { class CLogFile { friend class CLogTime; public: CLogFile(); ~CLogFile(); static void OutputFunc(const char *pMsg, int iLen); static void FlushFunc(void); //这个方法是用来将日志写入Log文件 void OutputLogFile(const char *pMsg, int iLen); void FlushLogFile(void); //单例模式 static CLogFile *instance(void); private: void createLogDir(); std::string getLogFileName(void); void openLogFile(void); //这个方法是用来重新创建一个新文件的, //我们不希望这个方法暴露出来(在类LogTime使用),声明 //为私有的,为了使类LogTime能调用,我们声明类LogTime //为类CLogFile的友元类 void updateLogFile(LOGDATE *pDate); LOGDATE m_Date; std::fstream m_LogFileStream; }; } #endif //#ifndef __LOG_FILE_H__ 7.LogFile.cpp

#include <string> #include <time.h> #include <iostream> #include "LogFile.h" #ifdef LINUX #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #else #include <io.h> #include <direct.h> #endif //#ifdef LINUX #if _MSC_VER #define snprintf _snprintf #endif namespace Log { #ifdef LINUX static const char arrLogFileDir[] = "./Logger"; #else static const char arrLogFileDir[] = "D:\\Logger"; #endif //#ifdef LINUX CLogFile::CLogFile() :m_Mutex() { time_t timep; struct tm *pTm; time(&timep); pTm = localtime(&timep); m_Date.m_iDay = pTm->tm_mday; m_Date.m_iMon = pTm->tm_mon + 1; m_Date.m_iYear = pTm->tm_year + 1900; createLogDir(); //创建存放Log的目录 openLogFile(); //打开对应日志文件 } CLogFile::~CLogFile() { } void CLogFile::OutputFunc(const char *pMsg, int iLen) { CLogFile *pLogFile = instance(); pLogFile->OutputLogFile(pMsg, iLen); } void CLogFile::FlushFunc(void) { CLogFile *pLogFile = instance(); pLogFile->FlushLogFile(); } void CLogFile::OutputLogFile(const char *pMsg, int iLen) { boost::mutex::scoped_lock lock(m_Mutex); if (m_pFile != NULL) ::fwrite(pMsg, 1, iLen, m_pFile); } void CLogFile::FlushLogFile(void) { boost::mutex::scoped_lock lock(m_Mutex); ::fflush(m_pFile); } CLogFile *CLogFile::instance(void) { static CLogFile tLogFile; return &tLogFile; } void CLogFile::createLogDir(void) { #ifdef LINUX if (access(arrLogFileDir, F_OK) == -1) #else if (_access(arrLogFileDir, 0) == -1) #endif //#ifdef LINUX { //不存在这个目录,创建它 #ifdef LINUX mkdir(arrLogFileDir, 0777); #else mkdir(arrLogFileDir); #endif } } std::string CLogFile::getLogFileName(void) { char arrBuffer[64]; memset(arrBuffer, 0x00, sizeof(arrBuffer)); snprintf(arrBuffer, sizeof(arrBuffer), "d-d-d-Log.txt", m_Date.m_iYear, m_Date.m_iMon, m_Date.m_iDay); return arrBuffer; } void CLogFile::openLogFile(void) { char arrBuffer[64]; std::string strLogFileName = getLogFileName(); memset(arrBuffer, 0x00, sizeof(arrBuffer)); #ifdef LINUX snprintf(arrBuffer, sizeof(arrBuffer), "%s/%s", arrLogFileDir, strLogFileName.c_str()); #else snprintf(arrBuffer, sizeof(arrBuffer), "%s\\%s", arrLogFileDir, strLogFileName.c_str()); #endif boost::mutex::scoped_lock lock(m_Mutex); m_pFile = fopen(arrBuffer, "a"); } void CLogFile::updateLogFile(LOGDATE *pDate) { if (pDate == NULL) return; if (m_Date.m_iYear != pDate->m_iYear || m_Date.m_iMon != pDate->m_iMon || m_Date.m_iDay != pDate->m_iDay) { //新的一天开始了,那么就,关闭之前的。重新创建个对应日期的日志文件。 m_Date.m_iYear = pDate->m_iYear; m_Date.m_iMon = pDate->m_iMon; m_Date.m_iDay = pDate->m_iDay; FlushLogFile(); fclose(m_pFile); m_pFile = NULL; openLogFile(); } } }

8.LogRealize.h

#ifndef __LOG_REALIZE_H__ #define __LOG_REALIZE_H__ #include "LogTime.h" #include "LogCommon.h" #include "LogStream.h" namespace Log { class CLogRealize { public: explicit CLogRealize(CFileName &SourceFile, const char *pLevel, int iLine); ~CLogRealize(); CLogStream &GetLogStream(void); //这个方法是在完成日志流的输出后填充文件名和行号的 void FinishLog(); //我们的实现上,日志流是先存储在固定的 //Buffer里面,等到整条日志流输出完毕。 //这个方法就是获取获取日志流 const char *GetLogStreamBuff(void); int GetLogStreamBuffLen(void); private: int m_iLine; //行号 CLogTime m_LogTimeStr; //Log时间类 CFileName m_SourceFileName; //文件名 CLogStream m_Stream; //LogStream类 }; } #endif //#ifndef __LOG_REALIZE_H__ 9.LogRealize.cpp

#include "LogRealize.h" namespace Log { CLogRealize::CLogRealize(CFileName &SourceFile, const char *pLevel, int iLine) :m_SourceFileName(SourceFile) , m_iLine(iLine) , m_LogTimeStr() , m_Stream() { //在构造的时候会填充日志头(其实就是时间和报警等级) //首先,输出打印日志时间到LogStream中的Buffer m_Stream << m_LogTimeStr.getLogTime(); //其次,输出日志等级 m_Stream << pLevel; } CLogRealize::~CLogRealize() { } CLogStream &CLogRealize::GetLogStream(void) { return m_Stream; } void CLogRealize::FinishLog() { //根据我们的日志格式安排,最后输出的是 //文件名即行号。这个方法是在Logger析构 //时候调用的,表示一条日志流的完成,可以 //输出到标准输出或文件中去。 m_Stream << " - " << m_SourceFileName.m_pFileName << ":" << m_iLine << "\n"; } const char *CLogRealize::GetLogStreamBuff(void) { return m_Stream.GetStreamBuff(); } int CLogRealize::GetLogStreamBuffLen(void) { return m_Stream.GetStreamBuffLen(); } } 10.Logger.h

#ifndef __LOGGER_H__ #define __LOGGER_H__ #include <memory> #include "LogStream.h" #include "LogCommon.h" namespace Log { class CLogRealize; //具体实现的向前声明 class CLogger { public: //DEBUG Level:指出细粒度信息事件对调试应用程序是非常有帮助的。 //INFO Level:消息在粗粒度级别上突出强调应用程序的运行 //WARN Level:表明会出现潜在错误的情况。 //ERROR Level:指出虽然发生错误事件,但仍然不影响系统的继续运行。 //FATAL Level:指出每个严重的错误事件将会导致应用程序的退出。 //当我们定义了日志的优先级之后,应用程序中比设置的优先级别低的 //都会被输出。 enum LogLevel {DEBUG, INFO, WARN, ERROR, FATAL}; typedef void(*OutputFunc)(const char *pMsg, int iLen); typedef void(*FlushFunc)(void); //设置和获取日志等级 static void setLogLevel(LogLevel level); static LogLevel getLogLevel(void); //设置输出模式的,支持3种输出模式 static void setOutputMode(int iMode); CLogger(CFileName SourceFile, LogLevel Level, int iLine); ~CLogger(); CLogStream &GetLogStream(void); private: static void setOutputFunc(OutputFunc Func); static void setFlushFunc(FlushFunc Func); static void OutputOutAndLog(const char *pMsg, int iLen); static void FlushAll(void); //使用Pimpl机制,解开类的使用接口和实现的耦合 std::shared_ptr<CLogRealize> m_spImpl; //使用智能指针来管理 LogLevel m_LogLevel; }; //这几个宏是用来设置输出模式的:输出到标准输出 输出到log文件 输出到标准输出和log文件 #define LOGGER_MODE_STDOUT 0x01 #define LOGGER_MODE_LOGFILE 0x02 #define LOGGER_MODE_OUTANDFILE (LOGGER_MODE_STDOUT | LOGGER_MODE_LOGFILE) #define LOG_DEBUG if (CLogger::getLogLevel() <= CLogger::DEBUG) \ CLogger(__FILE__, CLogger::DEBUG, __LINE__).GetLogStream() #define LOG_INFO if (CLogger::getLogLevel() <= CLogger::INFO) \ CLogger(__FILE__, CLogger::INFO, __LINE__).GetLogStream() #define LOG_WARN if (CLogger::getLogLevel() <= CLogger::WARN) \ CLogger(__FILE__, CLogger::WARN, __LINE__).GetLogStream() #define LOG_ERROR CLogger(__FILE__, CLogger::ERROR, __LINE__).GetLogStream() #define LOG_FATAL CLogger(__FILE__, CLogger::FATAL, __LINE__).GetLogStream() } #endif //#ifndef __LOGGER_H__ 11.Logger.cpp

#include <stdio.h> #include "Logger.h" #include "LogRealize.h" #include "LogFile.h" namespace Log { const char *arrLevel[] = { "[DEBUG] ", "[INFO] ", "[WARN] ", "[ERROR] ", "[FATAL] " }; CLogger::LogLevel g_LogLevel = CLogger::INFO; //默认的日志等级为INFO int g_OutputMode = LOGGER_MODE_STDOUT; //默认的输出模式为输出到标准输出 void DefauleOutout(const char *pMsg, int iLen) { ::fwrite(pMsg, 1, iLen, stdout); } void DefauleFlush(void) { ::fflush(stdout); } CLogger::OutputFunc g_OutputFunc = DefauleOutout; //默认的日志输出到标准输出 CLogger::FlushFunc g_FlushFunc = DefauleFlush; CLogger::CLogger(CFileName SourceFile, LogLevel Level, int iLine) :m_spImpl(new CLogRealize(SourceFile, arrLevel[Level], iLine)) ,m_LogLevel(Level) { } CLogger::~CLogger() { //当调用析构的时候,日志流填充下文件名和行号,就是完整的一条日志了 m_spImpl->FinishLog(); g_OutputFunc(m_spImpl->GetLogStreamBuff(), m_spImpl->GetLogStreamBuffLen()); if (m_LogLevel == FATAL) { //如果发生了FATAL错误,那么就终止程序。 //以便之后重启程序。 DefauleFlush(); //在此之前先冲刷缓冲区 abort(); } } CLogStream &CLogger::GetLogStream(void) { return m_spImpl->GetLogStream(); } CLogger::LogLevel CLogger::getLogLevel(void) { return g_LogLevel; } void CLogger::setLogLevel(LogLevel level) { g_LogLevel = level; } void CLogger::setOutputMode(int iMode) { g_OutputMode = iMode; if (g_OutputMode == LOGGER_MODE_STDOUT) { //事实上,默认的输出就是输出到标准输出,不做处理 return; } else if (g_OutputMode == LOGGER_MODE_LOGFILE) { //仅输出到日志文件 setOutputFunc(CLogFile::OutputFunc); setFlushFunc(CLogFile::FlushFunc); } else if (g_OutputMode == (LOGGER_MODE_STDOUT | LOGGER_MODE_LOGFILE)) { //输出到标准输出和log中去 setOutputFunc(OutputOutAndLog); setFlushFunc(FlushAll); } else { //nothing } } void CLogger::setOutputFunc(OutputFunc Output) { g_OutputFunc = Output; } void CLogger::setFlushFunc(FlushFunc Flush) { g_FlushFunc = Flush; } void CLogger::OutputOutAndLog(const char *pMsg, int iLen) { DefauleOutout(pMsg, iLen); //输出到标准输出 CLogFile::OutputFunc(pMsg, iLen); //输出到日志中 } void CLogger::FlushAll(void) { DefauleFlush(); CLogFile::FlushFunc(); } } 实现的代码到此为止。

转载请注明原文地址: https://www.6miu.com/read-66987.html

最新回复(0)