日志与策略模式

什么是设计模式

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;
}
相关推荐
苏三说技术32 分钟前
Claude Code从失控到起飞,只用了这些技巧
后端
长栎1 小时前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode2 小时前
Redis 在生产项目的使用
前端·后端
用户559822481222 小时前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode2 小时前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战2 小时前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha2 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn2 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端
用户762352425912 小时前
ShardingJDBC
后端
行者全栈架构师2 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端