什么是設計模式
IT?業 ,為了讓 菜雞們不太拖?佬的后腿, 于是?佬們針對?些經典的常?的場景, 給定了?些對應的解決?案, 這個就是? 設計模式?
日志認識
計算機中的?志是記錄系統和軟件運?中發?事件的?件,主要作?是監控運?狀態、記錄異常信 息,幫助快速定位問題并?持程序員進?問題修復。它是系統維護、故障排查和安全管理的重要? 具。
?志格式以下?個指標是必須得有的
- 時間戳
- ?志等級
- ?志內容
以下幾個指標是可選的
- 文件名行號
- 進程,線程相關id信息等
?志有現成的解決?案,如:spdlog、glog、Boost.Log、Log4cxx等等,我們依舊采??定義?志的方式。
這?我們采?設計模式-策略模式來進??志的設計,
策略模式是一種行為型設計模式,它允許在運行時選擇算法或行為。該模式將算法族定義為一組可互換的策略,使得算法可以獨立于使用它的客戶端變化。
策略模式基于以下設計原則:
封裝變化:將易變的算法部分單獨封裝
面向接口編程:定義策略接口,而不是具體實現
組合優于繼承:通過組合策略對象來獲得靈活性,而非通過繼承
策略模式包含三個主要角色:
Context(上下文):
維護對策略對象的引用
可以定義一個接口讓策略訪問它的數據
Strategy(策略接口):
定義所有支持的算法的公共接口
Context使用這個接口調用具體策略定義的算法
ConcreteStrategy(具體策略):
實現策略接口的具體算法
例子:
#include <iostream>
#include <memory>// 策略接口
class SortingStrategy {
public:virtual void sort(int* data, int size) const = 0;virtual ~SortingStrategy() = default;
};// 具體策略A:快速排序
class QuickSort : public SortingStrategy {
public:void sort(int* data, int size) const override {std::cout << "Sorting using QuickSort\n";// 實際快速排序實現...}
};// 具體策略B:冒泡排序
class BubbleSort : public SortingStrategy {
public:void sort(int* data, int size) const override {std::cout << "Sorting using BubbleSort\n";// 實際冒泡排序實現...}
};// 上下文類
class Sorter {
private:std::unique_ptr<SortingStrategy> strategy;public:explicit Sorter(std::unique_ptr<SortingStrategy> strategy) : strategy(std::move(strategy)) {}void setStrategy(std::unique_ptr<SortingStrategy> newStrategy) {strategy = std::move(newStrategy);}void executeSort(int* data, int size) {strategy->sort(data, size);}
};int main() {int data[] = {5, 2, 7, 1, 9};Sorter sorter(std::make_unique<QuickSort>());sorter.executeSort(data, 5); // 使用快速排序sorter.setStrategy(std::make_unique<BubbleSort>());sorter.executeSort(data, 5); // 改為冒泡排序return 0;
}
我們想要的?志格式如下:
[可讀性很好的時間] [?志等級] [進程pid] [打印對應?志的?件名][?號] - 消息內容,?持可
變參數
[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
log.hpp
#pragma once
#include <iostream>
#include <string>
#include "Mutex.hpp"
#include <filesystem> //c++17
#include <fstream> //c++文件流
#include <sstream> //c++字符串流
#include <memory>
#include <time.h>//基于策略模式的日志
namespace LogModule
{using namespace LockModule;// 獲取時間的函數std::string CurrentTime(){time_t time_stamp = ::time(nullptr); // 獲取時間戳struct tm curr;//_r代表可以重入,支持多線程localtime_r(&time_stamp, &curr); // 將時間戳轉化成可讀性較強的時間信息char buffer[1024];// bugsnprintf(buffer, sizeof(buffer), "%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 buffer;}// 日志構成兩個階段: 一.構建日志信息 二.刷新落盤screen / file(向哪里刷新)// 二. 刷新落盤// 1. 日志文件的默認路徑和名稱const std::string dafaultlogpath = "./log/";const std::string dafaultlogname = "log.txt";// 2. 日志等級enum class LogLevel{DEBUG = 1,INFO, // 正常的WARNNING,ERROR,FATAL // 致命的};std::string Level2String(LogLevel level){switch (level){case LogLevel::DEBUG:return "DEBUG";case LogLevel::INFO:return "INFO";case LogLevel::WARNNING:return "WARNNING";case LogLevel::ERROR:return "ERROR";case LogLevel::FATAL:return "FATAL";default:return "NONE";}}// 3.刷新策略class LogStrategy // 基類{public:virtual ~LogStrategy() = default; //虛析構函數(保證派生類對象能正確析構):確保通過基類指針刪除派生類對象時能正確調用派生類的析構函數virtual void SyncLog(const std::string &message) = 0;//純虛函數使得基類為抽象類 ,其派生類必須重構此函數才能構建對象};// 3.1控制臺策略class ConsoleLogStrategy : public LogStrategy{public:ConsoleLogStrategy(){}~ConsoleLogStrategy(){}void SyncLog(const std::string &message){// 屏幕也是臨界資源LockGuard lockguard(_mutex);std::cout << message << std::endl;}private:Mutex _mutex;};// 3.2文件級策略class FileLogStrategy : public LogStrategy{public:FileLogStrategy(const std::string &logpath = dafaultlogpath, const std::string &logname = dafaultlogname): _logpath(logpath),_logname(logname){LockGuard lockguard(_mutex);// 確認_logpath存在if (std::filesystem::exists(_logpath)){return;}try{std::filesystem::create_directories(_logpath);}catch (std::filesystem::filesystem_error &e){std::cerr << e.what() << "\n";}}~FileLogStrategy(){}void SyncLog(const std::string &message){LockGuard lockguard(_mutex);// c++文件操作std::string log = _logpath + _logname;// 創建一個ofstream文件輸出流對象,以追加模式打開日志文件std::ofstream out(log, std::ios::app); // 日志是追加寫入if (!out.is_open()){return;}out << message << "\n";out.close();}private:std::string _logpath;std::string _logname;Mutex _mutex; // 保證資源安全};// 一. 構建日志信息// 日志類 ,構建日志字符串(內部類實現) ,根據策略進行刷新class Logger{public:Logger(){// 默認使用控制臺刷新_strategy = std::make_shared<ConsoleLogStrategy>();}void EnableConsoleLog(){_strategy = std::make_shared<ConsoleLogStrategy>();}void EnableFileLog(){_strategy = std::make_shared<FileLogStrategy>();}~Logger(){}// 定義了內部類 一個logmessage就包含了一條完整的日志信息// 一條完整的日志信息: [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] + 日志的可變部分(<< "hello world" << 3.14 << a << b;)class LogMessage{public:LogMessage(LogLevel level, const std::string &filename, int line, Logger &logger): _currtime(CurrentTime()), _level(level), _pid(getpid()), _src_name(filename), _line(line), _logger(logger){// 用stringstream進行流式拼接std::stringstream ssbuffer;ssbuffer << "[" << _currtime << "] "<< "[" << Level2String(_level) << "] " // 我們想要字符串式的日志等級<< "[" << _pid << "] "<< "[" << _src_name << "] "<< "[" << _line << "] - ";_loginfo = ssbuffer.str();}// LOG(DEBUG) << "hello " << 3.14 << a << b;想要實現需要重載<<template <typename T>LogMessage &operator<<(const T &info) // 返回使用引用 (要保證后面的信息都拼接到同一個LogMessage){std::stringstream ss;ss << info;_loginfo += ss.str();return *this; // 返回自己}~LogMessage(){// 析構時,執行Logger所對應的根據指定策略進行刷新一條方法if (_logger._strategy)//設置策略了就刷新{_logger._strategy->SyncLog(_loginfo);}}private:std::string _currtime; // 時間LogLevel _level; // 日志等級pid_t _pid; // 進程pidstd::string _src_name; // 原文件名稱int _line; // 行號Logger &_logger; // 負責根據不同的策略進行刷新std::string _loginfo; // 一條完整的日志信息};// 仿函數重載() ,返回一個完整的日志信息// 故意沒有寫引用 ,就是要拷貝,返回臨時的LogMessage??? 臨時的LogMessage 自動析構時 自動刷新日志 LogMessage operator()(LogLevel level, const std::string &filename, int line){return LogMessage(level, filename, line, *this);}private:std::shared_ptr<LogStrategy> _strategy; // 日志的刷新方案// LogStrategy是純虛類 ,不能定義對象,能定義指針};// 使用Logger logger;#define LOG(level) logger(level, __FILE__, __LINE__) //__是預處理符 logger()運算符重載
#define ENABLE_CONSOLE_LOG() logger.EnableConsoleLog()
#define ENABLE_FILE_LOG() logger.EnableFileLog()
}
Main.cc
#include"log.hpp"using namespace LogModule;int main()
{//用C++版的流,實現可變參數//日志輸出格式//LOG(DEBUG) << "hello " << 3.14 << a << b;//會被替換成下面格式//logger(level ,__FILE__ ,__LINE__)<< "hello " << 3.14 << a << b; //()執行完構建一個臨時的LogMessage,臨時的LogMessage會執行<<//LogMessage<< "hello " << 3.14 << a << b; //LogMessage重載了<<ENABLE_FILE_LOG();LOG(LogLevel::INFO)<<"hello"<<666;return 0;
}