日志与策略模式

发布于:2025-06-02 ⋅ 阅读:(34) ⋅ 点赞:(0)

什么是设计模式


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


日志认识


计算机中的日志是记录系统和软件运行中发发事件的文件,主要作用是监控运行状态、记录异常信
息,帮助快速定位问题并支持程序员进行问题修复。它是系统维护、故障排查和安全管理的重要工
具。

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

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

以下几个指标是可选的

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

日志有现成的解决方案,如:spdlog、glog、Boost.Log、Log4cxx等等,我们依旧采用自定义日志的方式。
这里我们采用设计模式-策略模式来进行日志的设计,我们想要的日志格式如下:

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

#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

#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

#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

#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

#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

#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

#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

#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

#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;
}

 


网站公告

今日签到

点亮在社区的每一天
去签到