文章目錄
- 一. 前言
- 二. 基本功能
- 三. 代碼實作
- 1. fdoglogger.h
- 2. fdoglogger.cpp
- 四. 測驗用例
- 1. fdoglogger_test.cpp
一. 前言
哈嘍,自從實習以來很久沒有更文了,一是沒有時間,二是實習了之后突然發現自己能寫的東西也沒有多少了,趕上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 == "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++日志庫,用于日志服務,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/336185.html
標籤:其他
下一篇:三、區分存盤物件的不同版本
