1. 什么是設(shè)計(jì)模式
??it行業(yè)這么火, 涌入的人很多. 俗話說林子大了啥鳥都有. 大佬和菜雞們兩極分化的越來越嚴(yán)重. 為了讓菜雞們不太拖大佬的后腿, 于是大佬們針對(duì)?些經(jīng)典的常見的場景, 給定了?些對(duì)應(yīng)的解決?案, 這個(gè)就是設(shè)計(jì)模式。 ??在it行業(yè)中,設(shè)計(jì)模式(design patterns) 是一套被廣泛認(rèn)可的、用于解決軟件設(shè)計(jì)中常見問題的最佳實(shí)踐。它們提供了一種標(biāo)準(zhǔn)化的方法來處理特定的設(shè)計(jì)問題,并且可以幫助開發(fā)人員編寫更清晰、更具可維護(hù)性的代碼。
2. 日志認(rèn)識(shí)
??計(jì)算機(jī)中的日志是記錄系統(tǒng)和軟件運(yùn)行中發(fā)生事件的文件,主要作用是監(jiān)控運(yùn)行狀態(tài)、記錄異常信息,幫助快速定位問題并?持程序員進(jìn)?問題修復(fù)。它是系統(tǒng)維護(hù)、故障排查和安全管理的重要工具。 日志格式以下幾個(gè)指標(biāo)是必須得有的:
時(shí)間戳日志等級(jí)日志內(nèi)容
以下幾個(gè)指標(biāo)是可選的:
文件名行號(hào)進(jìn)程,線程相關(guān)id信息等 日志有現(xiàn)成的解決方案,如:spdlog、glog、Boost.Log、Log4cxx等等,我們依舊采用自定義日志的方式。這里我們采用設(shè)計(jì)模式-策略模式來進(jìn)行日志的設(shè)計(jì),我們想要的日志格式如下:代碼語言:JavaScript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
[可讀性很好的時(shí)間] [?志等級(jí)] [進(jìn)程pid] [打印對(duì)應(yīng)?志的?件名][?號(hào)] - 消息內(nèi)容,?持可變參數(shù)[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] - hello world[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [17] - hello world[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [18] - hello world[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [20] - hello world[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [21] - hello world[2024-08-04 12:27:03] [WARNING] [202938] [main.cc] [23] - hello world
3. 日志實(shí)現(xiàn)首先我們需要設(shè)置日志等級(jí):代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
// ?志等級(jí) enum class LogLevel { DEBUG, INFO, WARNING, ERROR, FATAL }; // ?志轉(zhuǎn)換成為字符串 std::string LogLevelToString(LogLevel level) { switch (level) { case LogLevel::DEBUG: return "DEBUG"; case LogLevel::INFO: return "INFO"; case LogLevel::WARNING: return "WARNING"; case LogLevel::ERROR: return "ERROR"; case LogLevel::FATAL: return "FATAL"; default: return "UNKNOWN"; } }
設(shè)置日志時(shí)間代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
// 根據(jù)時(shí)間戳,獲取可讀性較強(qiáng)的時(shí)間信息 std::string GetCurrTime() { time_t tm = time(nullptr); struct tm curr; localtime_r(&tm, &curr); char timebuffer[64]; snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d:%02d:%02d ", curr.tm_year + 1900,//這是因?yàn)檫@里的年份比實(shí)際年份少1900 curr.tm_mon+1,//這是因?yàn)樵路菔窃?~11 curr.tm_mday, curr.tm_hour, curr.tm_min, curr.tm_sec); return timebuffer; }
日志策略模式:
??有了準(zhǔn)備工作后,我們?cè)陂_始設(shè)計(jì)日志類之前還需要確定日志的策略模式——也就是日志是往控制臺(tái)上輸出還是文件中輸出。
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
// 策略模式,策略接? class LogStrategy { public: virtual ~LogStrategy() = default; // 策略的析構(gòu)函數(shù) virtual void SyncLog(const std::string &message) = 0; // 不同模式核?是刷新?式的不同 };
控制臺(tái)日志策略:代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
// 控制臺(tái)?志策略,就是?志只向顯?器打印,?便我們debug class ConsoleLogStrategy : public LogStrategy { public: void SyncLog(const std::string &message) override { LockGuard LockGuard(_mutex); std::cerr 文件日志策略:代碼語言:javascript<i class="icon-code"></i>代碼運(yùn)行次數(shù):<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>運(yùn)行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>復(fù)制<pre class="prism-token token line-numbers javascript"> class FileLogStrategy : public LogStrategy { public: // 構(gòu)造函數(shù),建?出來指定的?錄結(jié)構(gòu)和?件結(jié)構(gòu) FileLogStrategy(const std::string logpath = defaultpath, std::string logfilename = defaultname) : _logpath(logpath), _logfilename(logfilename) { LockGuard lockguard(_mutex); if (std::filesystem::exists(_logpath)) return; try { std::filesystem::create_directories(_logpath); } catch (const std::filesystem::filesystem_error &e) { std::cerr 具體日志類:<p>我們先確定日志策略模式,默認(rèn)是控制臺(tái)輸出;然后定義一個(gè)內(nèi)部類用來確定日志輸出的信息:</p>代碼語言:javascript<i class="icon-code"></i>代碼運(yùn)行次數(shù):<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>運(yùn)行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>復(fù)制<pre class="prism-token token line-numbers javascript"> // 具體的?志類 class Logger { public: Logger() { // 默認(rèn)使?顯?器策略,如果???次指明了策略,會(huì)釋放在申請(qǐng),測試的時(shí)候注意析構(gòu)次數(shù) UseConsoleStrategy(); } ~Logger() { } void UseConsoleStrategy() { _strategy = std::make_unique<consolelogstrategy>(); } void UseFileStrategy() { _strategy = std::make_unique<filelogstrategy>(); } class LogMessage { public: LogMessage(LogLevel type, std::string filename, int line, Logger &logger) : _curr_time(GetCurrTime()), _pid(getpid()), _filename(filename), _line(line), _logger(logger) { // stringstream不允許拷?,所以這?就當(dāng)做格式化功能使? std::stringstream ssbuffer; ssbuffer LogMessage &operatorSyncLog(_loginfo); } } private: LogLevel _type; // ?志等級(jí) std::string _curr_time; // ?志時(shí)間 pid_t _pid; // 寫??志的進(jìn)程ID std::string _filename; // 對(duì)應(yīng)的?件名 int _line; // 對(duì)應(yīng)的?件?號(hào) Logger &_logger; // 引?外部logger類, ?便使?策略進(jìn)?刷新 std::string _loginfo; // ?條合并完成的,完整的?志信息 }; LogMessage operator()(LogLevel type, std::string filename, int line) { return LogMessage(type, filename, line, *this); } private: std::unique_ptr<logstrategy> _strategy; };</logstrategy></filelogstrategy></consolelogstrategy>
最后將上述內(nèi)容放在一個(gè)命名空間LogModule內(nèi)部,并定義一個(gè)日志類對(duì)象:代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
#include#include #include #include #include #include #include // C++17, 需要?版本編譯器和-std=c++17#include #include "Mutex.hpp"namespace LogModule{ using namespace MutexModule; // 默認(rèn)路徑和?志名稱 const std::string defaultpath = "./log/"; const std::string defaultname = "log.txt"; // ?志等級(jí) enum class LogLevel { DEBUG, INFO, WARNING, ERROR, FATAL }; // ?志轉(zhuǎn)換成為字符串 std::string LogLevelToString(LogLevel level) { switch (level) { case LogLevel::DEBUG: return "DEBUG"; case LogLevel::INFO: return "INFO"; case LogLevel::WARNING: return "WARNING"; case LogLevel::ERROR: return "ERROR"; case LogLevel::FATAL: return "FATAL"; default: return "UNKNOWN"; } } // 根據(jù)時(shí)間戳,獲取可讀性較強(qiáng)的時(shí)間信息 std::string GetCurrTime() { time_t tm = time(nullptr); struct tm curr; localtime_r(&tm, &curr); char timebuffer[64]; snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d:%02d:%02d ", curr.tm_year + 1900, curr.tm_mon+1, curr.tm_mday, curr.tm_hour, curr.tm_min, curr.tm_sec); return timebuffer; } // 策略模式,策略接? class LogStrategy { public: virtual ~LogStrategy() = default; // 策略的構(gòu)造函數(shù) virtual void SyncLog(const std::string &message) = 0; // 不同模式核?是刷新?式的不同 }; // 控制臺(tái)?志策略,就是?志只向顯?器打印,?便我們debug class ConsoleLogStrategy : public LogStrategy { public: void SyncLog(const std::string &message) override { LockGuard LockGuard(_mutex); std::cerr (); } void UseFileStrategy() { _strategy = std::make_unique (); } class LogMessage { public: LogMessage(LogLevel type, std::string filename, int line, Logger &logger) : _curr_time(GetCurrTime()), _pid(getpid()), _filename(filename), _line(line), _logger(logger) { // stringstream不允許拷?,所以這?就當(dāng)做格式化功能使? std::stringstream ssbuffer; ssbuffer LogMessage &operatorSyncLog(_loginfo); } } private: LogLevel _type; // ?志等級(jí) std::string _curr_time; // ?志時(shí)間 pid_t _pid; // 寫??志的進(jìn)程ID std::string _filename; // 對(duì)應(yīng)的?件名 int _line; // 對(duì)應(yīng)的?件?號(hào) Logger &_logger; // 引?外部logger類, ?便使?策略進(jìn)?刷新 std::string _loginfo; // ?條合并完成的,完整的?志信息 }; LogMessage operator()(LogLevel type, std::string filename, int line) { return LogMessage(type, filename, line, *this); } private: std::unique_ptr _strategy; }; //定義日志類對(duì)象 Logger logger; // 使?宏,可以進(jìn)?代碼插?,?便隨時(shí)獲取?件名和?號(hào) #define LOG(type) logger(type, __FILE__, __LINE__) // 提供選擇使?何種?志策略的?法 #define ENABLE_CONSOLE_LOG_STRATEGY() logger.UseConsoleStrategy() #define ENABLE_FILE_LOG_STRATEGY() logger.UseFileStrategy()}
測試代碼:代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
#include <iostream>#include "Log.hpp"using namespace LogModule;void fun(){ int a = 10; LOG(LogLevel::FATAL) <p>結(jié)果如下:</p> <figure class=""><img src="https://img.php.cn/upload/article/001/503/042/174487142432849.jpg" alt="【Linux】日志設(shè)計(jì)模式與實(shí)現(xiàn)"></figure>4. 結(jié)語<p>??日志可以幫助我們快速準(zhǔn)確的了解程序運(yùn)行的狀況,出現(xiàn)的錯(cuò)誤以及相關(guān)內(nèi)容;同時(shí)日志的設(shè)計(jì)模式如解耦也值得我們學(xué)習(xí)。</p></iostream>