📝前言:
這篇文章我們來講講Linux——基于策略模式的簡單日志設計
🎬個人簡介:努力學習ing
📋個人專欄:Linux
🎀CSDN主頁 愚潤求學
🌄其他專欄:C++學習筆記,C語言入門基礎,python入門基礎,C++刷題專欄
這里寫目錄標題
- 一,認識日志
- 二,日志設計
- 1. 總體概述
- 2. Mylog.hpp
- 3. Main.cpp
- 4. 運行效果
一,認識日志
的日志是記錄系統和軟件運行中發生事件的文件,主要作用是監控運行狀態、記錄異常信息,幫助快速定位問題并支持程序員進行問題修復。它是系統維護、故障排查和安全管理的重要工具。
日志格式中通常包括:時間戳、日志等級、日志內容
還可能包括:、文件名、行號、進程,線程相關id信息等
盡管復制已經有了大佬寫好的現成的東西,但是本文還是采用設計模式- 略模式來進行一個簡單日志的設計
格式要求:
[時間] [?志等級] [進程pid] [對應?志的?件名][?號] - 消息內容(?持可變參數)
二,日志設計
1. 總體概述
我們的日志的關鍵設計包括以下兩點:
- 根據不同的策略,把日志內容輸出到不同的“文件”
- 顯示器文件
log.txt
日志文件
- 形成一條完整的日志內容
- 時間的獲取
- 日志等級的設計
- 進程PID
- 日志文件名和行號
- 消息內容的“插入”(插入日志信息的string里),同時要支持可變參數的插入
<<
2. Mylog.hpp
我們主要設計以下幾個類:
LogStrategy
策略模式基類,里面提供刷新方式SyncLog
的“標準”,需要子類繼承并重新給刷新方法實現多態- 子類1
ScreenLogStrategy
:往顯示器上刷新 - 子類2
FileLogStrategy
:往log
文件里面刷新
- 子類1
Log
日志主體,我們要實現的就是以后log << "日志內容"
就能寫入日志- 內部類
LogMessage
,采用RAII的設計思想,通過生命周期來控制日志內容的“寫入”(構造)和“刷新”(析構)
- 內部類
以下是具體的代碼:
#pragma once
#include <sstream>
#include <fstream>
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <memory>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include <filesystem> // C++17的文件庫, 需要?版本編譯器和-std=c++17#define FILEPATH "./log/"
#define FILENAME "log.txt"namespace tr
{// 枚舉類型,設置日志等級enum class LogLevel{DEBUG,INFO,WARNING,ERROR,FATAL};std::string Level2String(LogLevel loglevel){switch (loglevel){// C++11后枚舉類有嚴格的作用域,這里要指明是LogLevel::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";}}// 策略模式// 基類class LogStrategy{public:virtual ~LogStrategy() = default;virtual void SyncLog(std::string &message) = 0;};// 策略 1: 刷新到屏幕上class ScreenLogStrategy : public LogStrategy{public:void SyncLog(std::string &message) override{_mutex.lock();std::cerr << message << std::endl; // 打印到 cerr 上可以立即刷新_mutex.unlock();}~ScreenLogStrategy(){std::cout << "~ScreenLogStrategy()" << '\n';}private:std::mutex _mutex; // 用 C++ 的鎖對象};// 策略 2: 刷新到日志文件中class FileLogStrategy : public LogStrategy{public:FileLogStrategy(const std::string &logpath = "./", const std::string logname = "log.txt"): _logpath(logpath),_logname(logname){if (std::filesystem::exists(_logpath))return;try{std::filesystem::create_directories(_logpath); // 如果拋異常拋的是:const std::exception 類型的}catch (const std::exception &e) // 用基類捕獲所有異常{std::cerr << e.what() << '\n';}}void SyncLog(std::string &message) override{_mutex.lock();std::string logfile = _logpath + _logname;std::ofstream outfile(logfile, std::ios_base::out | std::ios_base::app); // 文件不存在會創建if (!outfile.is_open())return;outfile << message << '\n';_mutex.unlock();}private:std::string _logpath;std::string _logname;std::mutex _mutex;};// 日志主體class Log{public:Log(){UseScreenLogStrategy(); // 默認使用策略 1}~Log(){}void UseScreenLogStrategy(){_logstrategy = std::make_unique<ScreenLogStrategy>();}void UseFileLogStrategy(){_logstrategy = std::make_unique<FileLogStrategy>();}// 日志信息(內置類)// 為了后續實現 Mylog 的 operator() 重載的時候,返回臨時變量// 然后利用臨時變量的每行生命周期來實現 logmessage 的刷新class LogMessage{public:LogMessage(LogLevel type, std::string &filename, int line, Log& loger): _level(type),_pid(getpid()),_filename(filename),_time(GetTime()),_line(line),_loger(loger){std::stringstream ss;ss << "[" << _time << "]"<< "[" << Level2String(_level) << "]"<< "[" << _pid << "]"<< "[" << _filename << "]"<< "[" << _line << "]";_loginfo = ss.str(); // 日志的左半部分}~LogMessage() // 生命周期結束,刷新日志{if(_loger._logstrategy)_loger._logstrategy->SyncLog(_loginfo);}std::string GetTime(){time_t tm = time(nullptr); // 時間戳struct tm curr;localtime_r(&tm, &curr); // 傳入時間戳,會輸出一個 struct tm 里面記錄著時間char timebuffer[64]; // 用來保存格式化后的時間信息snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d:%02d:%02d",curr.tm_year + 1900,curr.tm_mon,curr.tm_mday,curr.tm_hour,curr.tm_min,curr.tm_sec);return timebuffer;}// 重載流插入,為了讓 Log 能支持<<// 底層是將插入的日志的右半部分信息添加到 _loginfotemplate <typename T>LogMessage &operator<<(const T &info){std::string ss = info;_loginfo += ss; //return *this; // 返回自己,實現多次 << }private:LogLevel _level;pid_t _pid;std::string _filename;std::string _time;int _line;std::string _loginfo; // 整條日志信息Log &_loger; // 外部類對象,用來調用刷新};// Log 的仿函數,特意返回臨時變量// 利用 RAII 的設計特點,創建一個LogMessage臨時對象// 在構造的時候,準備好左半部分, 在 << 的時候準備好 右半部分,最后在該行結束時,生命周期結束,刷新日志LogMessage operator()(LogLevel level, std::string filename, int line){return LogMessage(level, filename, line, *this);}private:std::unique_ptr<LogStrategy> _logstrategy;};Log logger; // 定義全局對象// 使?宏,可以進?代碼插?,?便隨時獲取?件名和?號#define LOG(type) logger(type, __FILE__, __LINE__) // __FILE__ 和 __LINE__ 可以自動獲取文件名和行號// 提供選擇使?何種日志策略的?法#define ENABLE_CONSOLE_LOG_STRATEGY() logger.UseScreenLogStrategy()#define ENABLE_FILE_LOG_STRATEGY() logger.UseFileLogStrategy()
}
3. Main.cpp
測試代碼
#include "Mylog.hpp"using namespace tr;
int main()
{// ENABLE_CONSOLE_LOG_STRATEGY();ENABLE_FILE_LOG_STRATEGY();LOG(LogLevel::DEBUG) << "hello world";LOG(LogLevel::ERROR) << "hello world";LOG(LogLevel::FATAL) << "hello world";LOG(LogLevel::INFO) << "hello world";LOG(LogLevel::INFO) << "hello world";return 0;
}
ENABLE_CONSOLE_LOG_STRATEGY()
:選擇往屏幕刷新的策略ENABLE_FILE_LOG_STRATEGY()
:選擇往文件里面刷新
4. 運行效果
🌈我的分享也就到此結束啦🌈
要是我的分享也能對你的學習起到幫助,那簡直是太酷啦!
若有不足,還請大家多多指正,我們一起學習交流!
📢公主,王子:點贊👍→收藏?→關注🔍
感謝大家的觀看和支持!祝大家都能得償所愿,天天開心!!!