一、簡介
在軟件開發過程中,開發人員經常需要在程序不同階段或者不同模塊中輸出一些調試信息,以方便查找問題以及調試程序。而程序的打印日誌是一種非常便捷、常用的方法。
C++是一門強大的編程語言,擁有良好的性能和靈活的特性,在日誌記錄功能方面可以藉助多種不同的庫實現,而利用C++11標準庫提供的特性,開發C++日誌記錄庫是一種簡單、輕量、靈活的方式。
二、如何實現日誌記錄
可以採用printf()函數輸出日誌信息,但這種方法有以下缺點:
- 無法動態控制日誌級別
- 無法更改日誌格式
- 無法在不同平台上實現
因此我們需要使用C++日誌庫來完成這些功能,例如log4cpp、log4cplus、glog等。但這些庫存在以下缺點:
- 依賴性強,容易出現編譯鏈接錯誤
- 使用方法複雜,需要較長的學習周期
- 庫文件較大,佔用磁盤空間
因此,我們可以自己開發一個輕量、簡單的C++日誌記錄庫。
三、實現
下面是利用C++11標準庫實現的一個簡單的日誌記錄庫:
#include <iostream> #include <fstream> #include <chrono> using namespace std; //日誌類型 enum log_type { DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3, }; //獲取日誌級別字符串 string get_log_level(log_type level) { static const string levels[] = {"DEBUG", "INFO", "WARN", "ERROR"}; return levels[level]; } //獲取當前時間並返回字符串格式 string get_time() { time_t now_time = chrono::system_clock::to_time_t(chrono::system_clock::now()); string time_str = ctime(&now_time); //去掉末尾的\n time_str.pop_back(); return time_str; } //日誌記錄函數 void log(log_type level, const string& message) { static const string file_name = "log.txt"; //打開文件 ofstream ofs(file_name, ios_base::app); if (!ofs) { cerr << "Failed to open file: " << file_name << endl; return; } //獲取時間和日誌級別 string time_str = get_time(); string level_str = get_log_level(level); //寫入日誌 ofs << "[" << time_str << "][" << level_str << "] " << message << endl; }
以上的代碼中,我們定義了日誌類型log_type和相應的字符串表示。然後定義了一個函數get_time(),用於獲取當前時間並返回字符串格式的時間。
接下來是最重要的函數log(log_type level, const string& message),該函數用於記錄日誌信息。在其中,我們首先指定了日誌存儲的文件名log.txt,並在函數的開頭打開文件並檢查是否打開成功。如果出錯,函數將不執行任何操作。然後,在函數的主體中,我們獲取當前時間和日誌級別,並將這些信息連同日誌信息一起寫入到文件中。
最後是一個簡單的示例,該示例記錄了三個不同級別的日誌:
int main() { log(DEBUG, "This is a debug message."); log(INFO, "This is an info message."); log(WARN, "This is a warning message."); log(ERROR, "This is an error message."); return 0; }
該示例記錄了四條日誌,每條日誌都包括了日誌級別、時間戳和日誌信息,將會被寫入log.txt中:
[Sat Nov 21 14:10:57 2020][DEBUG] This is a debug message. [Sat Nov 21 14:10:57 2020][INFO] This is an info message. [Sat Nov 21 14:10:57 2020][WARN] This is a warning message. [Sat Nov 21 14:10:57 2020][ERROR] This is an error message.
四、擴展
上面的C++日誌庫只具有基本的功能,如果想要更好地適應不同需求,我們可能需要對其進行一些擴展。
以下是對C++日誌庫擴展的一些常見需求:
1. 動態設置日誌級別
在實際開發中,我們可能希望可以在運行時動態更改日誌級別,例如實現一個命令行參數–log-level,使得程序可以接受不同的日誌級別。
class Logger { public: static Logger& instance() { static Logger logger; return logger; } void set_level(log_type level) { level_ = level; } void log(log_type level, const string& message) { if (level >= level_) { //do log } } private: log_type level_ = INFO; };
以上代碼中,我們實現了一個Logger類,並將其設計為單例模式,使用instance()方法獲取唯一實例。然後,我們添加了一個set_level()方法,用於動態設置日誌級別,並在log()方法中增加了一個級別判斷。在代碼中,我們定義了一個全局日誌級別變量level_,其默認值為INFO。
2. 日誌異步寫入
如果我們想要在日誌記錄和程序運行之間取得更好的平衡,我們可能會選擇讓程序異步寫入日誌文件。這樣可以提高程序的響應性並提高程序的整體效率。
class AsyncLogger { public: AsyncLogger() { //start logger thread } void log(log_type level, const string& message) { lock_guard lock(mutex_); queue_.emplace(level, message); } ~AsyncLogger() { //stop logger thread } private: void logger_thread() { while (true) { unique_lock lock(mutex_); condition_.wait(lock, [this]() { return !queue_.empty() || stop_; }); if (stop_ && queue_.empty()) { break; } auto log = queue_.front(); queue_.pop(); lock.unlock(); //write log to file } } mutex mutex_; condition_variable condition_; queue<pair<log_type, string>> queue_; bool stop_ = false; thread logger_thread_; };
以上代碼中,我們實現了一個AsyncLogger類,該類具有一個內部隊列和一個工作線程。用戶通過調用log()方法將日誌消息寫入隊列,而工作線程負責將這些消息異步寫入到日誌文件中。在類的析構函數中,停止工作線程。具有停止標誌stop_,當設置為True時,工作線程會退出。
3. 預處理的日誌消息
預處理的日誌消息允許我們將日誌消息轉換為其它字符序列或格式。這樣,我們就可以更靈活地控制日誌消息的格式和記錄方式。
#define LOG(level, message) \ do {\ stringstream ss;\ ss << message;\ log(level, ss.str()); \ } while(0)
以上代碼中,我們使用了一個C++的預處理器宏LOG(),該宏接受兩個參數:一個日誌級別和一個待記錄的信息。當調用該宏時,宏展開為一個logg()函數調用,該函數將日誌級別和記錄的信息進行了連接。
五、總結
在這篇文章中,我們介紹了如何使用C++11標準庫實現一個簡單的日誌記錄庫,該庫可以直接使用,也可以基於需求進行擴展。實現一個簡單的日誌庫並不難,我們只需要一些C++語言基礎和文件流操作。但一旦你要進行擴展,例如添加異步寫入、日誌輪替等功能,那麼你就需要更深入地了解C++11標準庫的使用以及多線程編程,這是日誌實現中難點之一。
原創文章,作者:EZJW,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/145149.html