日志与策略模式

什么是设计模式

IT行业这么火, 涌入的人很多. 俗话说林子大了啥鸟都有. 大佬和菜鸡们两极分化的越来越严重. 为了让菜鸡们不太拖大佬的后腿, 于是大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是 设计模式

日志认识

计算机中的日志是记录系统和软件运行中发发事件的文件,主要作用是监控运行状态、记录异常信

息,帮助快速定位问题并支持程序员进行问题修复。它是系统维护、故障排查和安全管理的重要工

具。

日志格式以下几个指标是必须得有的

  • 时间戳
  • 日志等级
  • 日志内容

以下几个指标是可选的

  • 文件名行号
  • 进程,线程相关id信息等

日志有现成的解决方案,如:spdlog、glog、Boost.Log、Log4cxx等等,我们依旧采用自定义日志的方式。

这里我们采用设计模式-策略模式来进行日志的设计,我们想要的日志格式如下:

cpp 复制代码
[可读性很好的时间] [⽇志等级] [进程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

日志功能:

  1. 形成完整日志
  2. 刷新到目标文件(显示器,指定文件打印日志)

多态实现两种策略的实现

Log.hpp

cpp 复制代码
#ifndef __LOG_HPP__
#define __LOG_HPP__

#include <iostream>
#include <string>
#include "Mutex.hpp"
#include <filesystem>
#include <fstream>
namespace LogModule
{
    const std::string sep = "\r\n";
    using namespace MutexModule;
    // 2.刷新策略
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 显示器刷新日志的策略
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy() {}
        ~ConsoleLogStrategy() {}
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::cout << message << sep;
        }

    private:
        Mutex _mutex;
    };

    // 缺省文件路径以及文件本身
    const std::string defaultpath = "./log";
    const std::string defaultfile = "my.log";
    // 文件刷新日志的策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path), _file(file)
        {
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_path)) // 判断路径是否存在
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
        }

        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
            std::ofstream out(filename, std::ios::app); // 追加写入
            if (!out.is_open())
            {
                return;
            }
            out << message << sep;
            out.close();
        }
        ~FileLogStrategy() {}

    private:
        Mutex _mutex;
        std::string _path; // 日志文件的路径
        std::string _file; // 要打印的日志文件
    };

}

#endif

Main.cc

cpp 复制代码
#include <iostream>
#include "Log.hpp"
#include <memory>

using namespace LogModule;
int main()
{   // 显示器刷新
    std::unique_ptr<LogStrategy> strategy1 = std::make_unique<ConsoleLogStrategy>(); // c++14
    strategy1->SyncLog("hello log1!");
    // 指定文件刷新
    std::unique_ptr<LogStrategy> strategy2 = std::make_unique<FileLogStrategy>(); // c++14
    strategy2->SyncLog("hello log2!");
    return 0;
}

形成完整日志

Log.hpp

cpp 复制代码
#ifndef __LOG_HPP__
#define __LOG_HPP__

#include <iostream>
#include <string>
#include "Mutex.hpp"
#include <filesystem>
#include <fstream>
#include <memory>
#include <unistd.h>
#include <sstream>

namespace LogModule
{
    const std::string sep = "\r\n";
    using namespace MutexModule;
    // 2.刷新策略
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 显示器刷新日志的策略
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy() {}
        ~ConsoleLogStrategy() {}
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::cout << message << sep;
        }

    private:
        Mutex _mutex;
    };

    // 缺省文件路径以及文件本身
    const std::string defaultpath = "./log";
    const std::string defaultfile = "my.log";
    // 文件刷新日志的策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path), _file(file)
        {
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_path)) // 判断路径是否存在
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
        }

        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
            std::ofstream out(filename, std::ios::app); // 追加写入
            if (!out.is_open())
            {
                return;
            }
            out << message << sep;
            out.close();
        }
        ~FileLogStrategy() {}

    private:
        Mutex _mutex;
        std::string _path; // 日志文件的路径
        std::string _file; // 要打印的日志文件
    };

    // 形成日志等级
    enum class Loglevel
    {
        DEBUG,
        INIF,
        WARNING,
        ERROR,
        FATAL
    };
    std::string Level2Str(Loglevel level)
    {
        switch (level)
        {
        case Loglevel::DEBUG:
            return "DEBUG";
        case Loglevel::INIF:
            return "INIF";
        case Loglevel::WARNING:
            return "WARNING";
        case Loglevel::ERROR:
            return "ERROR";
        case Loglevel::FATAL:
            return "FATAL";
        default:
            return "UNKNOWN";
        }
    }

    std::string GetTimeStamp()
    {
        return "XXX";
    }
    class Logger
    {
    public:
        Logger()
        {
            EnableConsoleLogStrategy();
        }
        // 选择某种策略
        // 1.文件
        void EnableFileLogStrategy()
        {
            _ffush_strategy = std::make_unique<FileLogStrategy>();
        }
        // 显示器
        void EnableConsoleLogStrategy()
        {
            _ffush_strategy = std::make_unique<ConsoleLogStrategy>();
        }

        
        // 表示的是未来的一条日志
        class LogMessage
        {
        public:
            LogMessage(Loglevel &level, std::string &src_name, int line_number, Logger &logger)
                : _curr_time(GetTimeStamp())
                , _level(level)
                , _pid(getpid())
                , _src_name(src_name)
                , _line_number(line_number)
                , _logger(logger)
            {
                // 合并左半部分
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << Level2Str(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _src_name << "] "
                   << "[" << _line_number << "] "
                   << "- ";
                _loginfo = ss.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                // 右半部分,可变
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }
            ~LogMessage()
            {
                if (_logger._ffush_strategy)
                {
                    _logger._ffush_strategy->SyncLog(_loginfo);
                }
            }
           

        private:
            std::string _curr_time; // 日志时间
            Loglevel _level;        // 日志状态
            pid_t _pid;             // 进程pid
            std::string _src_name;  // 文件名称
            int _line_number;       // 对应的行号
            std::string _loginfo;   // 合并之后的一条完整信息
            Logger &_logger;
        };
         LogMessage operator()(Loglevel level, std::string src_name, int line_number)
            {
                return LogMessage(level, src_name, line_number, *this);
            }
        ~Logger() {}

    private:
        std::unique_ptr<LogStrategy> _ffush_strategy;
    };
}

#endif

Main.cc

cpp 复制代码
#include <iostream>
#include "Log.hpp"
#include <memory>

using namespace LogModule;
int main()
{
    Logger log;
    //显示器打印
    log(Loglevel::DEBUG,"Main.cc",10)<<"hello log!";
    log(Loglevel::DEBUG,"Main.cc",10)<<"hello log!";
    log(Loglevel::DEBUG,"Main.cc",10)<<"hello log!";
    log(Loglevel::DEBUG,"Main.cc",10)<<"hello log!";
    log(Loglevel::DEBUG,"Main.cc",10)<<"hello log!";
    log(Loglevel::DEBUG,"Main.cc",10)<<"hello log!";
    
    //日志文件打印
    log.EnableFileLogStrategy();
    log(Loglevel::DEBUG,"Main.cc",10)<<"hello log!";
    log(Loglevel::DEBUG,"Main.cc",10)<<"hello log!";
    log(Loglevel::DEBUG,"Main.cc",10)<<"hello log!";
    log(Loglevel::DEBUG,"Main.cc",10)<<"hello log!";
    log(Loglevel::DEBUG,"Main.cc",10)<<"hello log!";
    log(Loglevel::DEBUG,"Main.cc",10)<<"hello log!";
    return 0;
}

使用宏简化代码

Log.hpp

cpp 复制代码
#ifndef __LOG_HPP__
#define __LOG_HPP__

#include <iostream>
#include <string>
#include "Mutex.hpp"
#include <filesystem>
#include <fstream>
#include <memory>
#include <unistd.h>
#include <sstream>

namespace LogModule
{
    const std::string sep = "\r\n";
    using namespace MutexModule;
    // 2.刷新策略
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 显示器刷新日志的策略
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy() {}
        ~ConsoleLogStrategy() {}
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::cout << message << sep;
        }

    private:
        Mutex _mutex;
    };

    // 缺省文件路径以及文件本身
    const std::string defaultpath = "./log";
    const std::string defaultfile = "my.log";
    // 文件刷新日志的策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path), _file(file)
        {
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_path)) // 判断路径是否存在
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
        }

        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
            std::ofstream out(filename, std::ios::app); // 追加写入
            if (!out.is_open())
            {
                return;
            }
            out << message << sep;
            out.close();
        }
        ~FileLogStrategy() {}

    private:
        Mutex _mutex;
        std::string _path; // 日志文件的路径
        std::string _file; // 要打印的日志文件
    };

    // 形成日志等级
    enum class Loglevel
    {
        DEBUG,
        INIF,
        WARNING,
        ERROR,
        FATAL
    };
    std::string Level2Str(Loglevel level)
    {
        switch (level)
        {
        case Loglevel::DEBUG:
            return "DEBUG";
        case Loglevel::INIF:
            return "INIF";
        case Loglevel::WARNING:
            return "WARNING";
        case Loglevel::ERROR:
            return "ERROR";
        case Loglevel::FATAL:
            return "FATAL";
        default:
            return "UNKNOWN";
        }
    }

    std::string GetTimeStamp()
    {
        return "XXX";
    }
    class Logger
    {
    public:
        Logger()
        {
            EnableConsoleLogStrategy();
        }
        // 选择某种策略
        // 1.文件
        void EnableFileLogStrategy()
        {
            _ffush_strategy = std::make_unique<FileLogStrategy>();
        }
        // 显示器
        void EnableConsoleLogStrategy()
        {
            _ffush_strategy = std::make_unique<ConsoleLogStrategy>();
        }

        
        // 表示的是未来的一条日志
        class LogMessage
        {
        public:
            LogMessage(Loglevel &level, std::string &src_name, int line_number, Logger &logger)
                : _curr_time(GetTimeStamp())
                , _level(level)
                , _pid(getpid())
                , _src_name(src_name)
                , _line_number(line_number)
                , _logger(logger)
            {
                // 合并左半部分
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << Level2Str(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _src_name << "] "
                   << "[" << _line_number << "] "
                   << "- ";
                _loginfo = ss.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                // 右半部分,可变
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }
            ~LogMessage()
            {
                if (_logger._ffush_strategy)
                {
                    _logger._ffush_strategy->SyncLog(_loginfo);
                }
            }
           

        private:
            std::string _curr_time; // 日志时间
            Loglevel _level;        // 日志状态
            pid_t _pid;             // 进程pid
            std::string _src_name;  // 文件名称
            int _line_number;       // 对应的行号
            std::string _loginfo;   // 合并之后的一条完整信息
            Logger &_logger;
        };
         LogMessage operator()(Loglevel level, std::string src_name, int line_number)
            {
                return LogMessage(level, src_name, line_number, *this);
            }
        ~Logger() {}

    private:
        std::unique_ptr<LogStrategy> _ffush_strategy;
    };
    //全局日志对象
    Logger logger;

    //使用宏,简化用户操作,获取文件名和行号
    // __FILE__  一个宏,替换完成后目标文件的文件名
    // __LINE__  一个宏,替换完成后目标文件对应的行号
    #define LOG(level) logger(level,__FILE__,__LINE__) 
    #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
    #define Enable_File_Log_Strategy()    logger.EnableFileLogStrategy()
     
}

#endif

Main.cc

cpp 复制代码
#include <iostream>
#include "Log.hpp"
#include <memory>

using namespace LogModule;
int main()
{
    Logger log;
    //显示器打印
    Enable_Console_Log_Strategy();
    LOG(Loglevel::DEBUG)<<"hello"<<" log!";
    LOG(Loglevel::DEBUG)<<"hello"<<" log!";
    LOG(Loglevel::DEBUG)<<"hello"<<" log!";

    //日志文件打印
    Enable_File_Log_Strategy();
    LOG(Loglevel::DEBUG)<<"hello"<<" log!";
    LOG(Loglevel::DEBUG)<<"hello"<<" log!";
    LOG(Loglevel::DEBUG)<<"hello"<<" log!";
   
    return 0;
}

补上时间,收工

完整日志实现代码

Log.hpp

cpp 复制代码
#ifndef __LOG_HPP__
#define __LOG_HPP__

#include <iostream>
#include <string>
#include "Mutex.hpp"
#include <filesystem>
#include <fstream>
#include <memory>
#include <unistd.h>
#include <sstream>
#include<ctime>

namespace LogModule
{
    const std::string sep = "\r\n";
    using namespace MutexModule;
    // 2.刷新策略
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 显示器刷新日志的策略
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy() {}
        ~ConsoleLogStrategy() {}
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::cout << message << sep;
        }

    private:
        Mutex _mutex;
    };

    // 缺省文件路径以及文件本身
    const std::string defaultpath = "./log";
    const std::string defaultfile = "my.log";
    // 文件刷新日志的策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path), _file(file)
        {
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_path)) // 判断路径是否存在
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
        }

        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
            std::ofstream out(filename, std::ios::app); // 追加写入
            if (!out.is_open())
            {
                return;
            }
            out << message << sep;
            out.close();
        }
        ~FileLogStrategy() {}

    private:
        Mutex _mutex;
        std::string _path; // 日志文件的路径
        std::string _file; // 要打印的日志文件
    };

    // 形成日志等级
    enum class Loglevel
    {
        DEBUG,
        INIF,
        WARNING,
        ERROR,
        FATAL
    };
    std::string Level2Str(Loglevel level)
    {
        switch (level)
        {
        case Loglevel::DEBUG:
            return "DEBUG";
        case Loglevel::INIF:
            return "INIF";
        case Loglevel::WARNING:
            return "WARNING";
        case Loglevel::ERROR:
            return "ERROR";
        case Loglevel::FATAL:
            return "FATAL";
        default:
            return "UNKNOWN";
        }
    }

    std::string GetTimeStamp()
    {
       time_t cuur =time(nullptr);
       struct tm curr_tm;
       localtime_r(&cuur,&curr_tm);
       char buffer[128];
       snprintf(buffer,sizeof(buffer),"%4d-%02d-%02d %02d:%02d:%02d",
       curr_tm.tm_year+1900,
       curr_tm.tm_mon+1,
       curr_tm.tm_mday,
       curr_tm.tm_hour,
       curr_tm.tm_min,
       curr_tm.tm_sec
       );
       return buffer;
    }
    class Logger
    {
    public:
        Logger()
        {
            EnableConsoleLogStrategy();
        }
        // 选择某种策略
        // 1.文件
        void EnableFileLogStrategy()
        {
            _ffush_strategy = std::make_unique<FileLogStrategy>();
        }
        // 显示器
        void EnableConsoleLogStrategy()
        {
            _ffush_strategy = std::make_unique<ConsoleLogStrategy>();
        }

        
        // 表示的是未来的一条日志
        class LogMessage
        {
        public:
            LogMessage(Loglevel &level, std::string &src_name, int line_number, Logger &logger)
                : _curr_time(GetTimeStamp())
                , _level(level)
                , _pid(getpid())
                , _src_name(src_name)
                , _line_number(line_number)
                , _logger(logger)
            {
                // 合并左半部分
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << Level2Str(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _src_name << "] "
                   << "[" << _line_number << "] "
                   << "- ";
                _loginfo = ss.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                // 右半部分,可变
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }
            ~LogMessage()
            {
                if (_logger._ffush_strategy)
                {
                    _logger._ffush_strategy->SyncLog(_loginfo);
                }
            }
           

        private:
            std::string _curr_time; // 日志时间
            Loglevel _level;        // 日志状态
            pid_t _pid;             // 进程pid
            std::string _src_name;  // 文件名称
            int _line_number;       // 对应的行号
            std::string _loginfo;   // 合并之后的一条完整信息
            Logger &_logger;
        };
         LogMessage operator()(Loglevel level, std::string src_name, int line_number)
            {
                return LogMessage(level, src_name, line_number, *this);
            }
        ~Logger() {}

    private:
        std::unique_ptr<LogStrategy> _ffush_strategy;
    };
    //全局日志对象
    Logger logger;

    //使用宏,简化用户操作,获取文件名和行号
    // __FILE__  一个宏,替换完成后目标文件的文件名
    // __LINE__  一个宏,替换完成后目标文件对应的行号
    #define LOG(level) logger(level,__FILE__,__LINE__) 
    #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
    #define Enable_File_Log_Strategy()    logger.EnableFileLogStrategy()
     
}

#endif

Mutex.hpp

cpp 复制代码
#include <pthread.h>
#include <iostream>
namespace MutexModule
{
    class Mutex
    {
    public:
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr);
        }

        void Lock()
        {
            int n = pthread_mutex_lock(&_mutex);
            (void)n;
        }
        void Unlock()
        {
            int n = pthread_mutex_unlock(&_mutex);
            (void)n;
        }
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex);
        }
        pthread_mutex_t *get()
        {
            return &_mutex;
        }
    private:
        pthread_mutex_t _mutex;
    };
    class LockGuard
    {
        public:
        LockGuard(Mutex &mutex):_mutex(mutex)
        {
        _mutex.Lock();
        }
        ~LockGuard()
        {
            _mutex.Unlock();
        }
        private:
        Mutex &_mutex;
    };
}

Main.cc

cpp 复制代码
#include <iostream>
#include "Log.hpp"
#include <memory>

using namespace LogModule;
int main()
{
    Logger log;
    //显示器打印
    Enable_Console_Log_Strategy();
    LOG(Loglevel::DEBUG)<<"hello"<<" log!";
    LOG(Loglevel::DEBUG)<<"hello"<<" log!";
    LOG(Loglevel::DEBUG)<<"hello"<<" log!";

    //日志文件打印
    Enable_File_Log_Strategy();
    LOG(Loglevel::DEBUG)<<"hello"<<" log!";
    LOG(Loglevel::DEBUG)<<"hello"<<" log!";
    LOG(Loglevel::DEBUG)<<"hello"<<" log!";
   
    return 0;
}
相关推荐
whitepure7 分钟前
万字详解Java中的面向对象(一)——设计原则
java·后端
autumnTop7 分钟前
为什么访问不了同事的服务器或者ping不通地址了?
前端·后端·程序员
后台开发者Ethan24 分钟前
Python需要了解的一些知识
开发语言·人工智能·python
小米里的大麦25 分钟前
022 基础 IO —— 文件
linux
Xの哲學29 分钟前
Perf使用详解
linux·网络·网络协议·算法·架构
门前灯30 分钟前
Linux系统之iprconfig 命令详解
linux·运维·服务器·iprconfig
用户67570498850231 分钟前
SQL 判断是否“存在”?99% 的人还在写错!
后端
PetterHillWater36 分钟前
12 MCP Servers的介绍
后端·aigc·mcp
杨杨杨大侠40 分钟前
02 - 核心模型设计 🧩
后端