【Linux笔记】——简单实习一个日志项目

🔥个人主页🔥:孤寂大仙V

🌈收录专栏🌈:Linux

🌹往期回顾🌹: 【Linux笔记】------线程同步信号量与环形队列生产者消费者模型的实现(PV操作)

🔖流水不争,争的是滔滔不息


一、日志的简介

程序员的日志用于记录开发过程中的关键步骤、决策和问题。通过详细记录,开发者可以追踪代码的演变过程,了解每个功能或模块的实现细节。这种记录有助于在项目后期进行回顾和总结,特别是在需要重构或优化代码时。

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

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

以下几个指标是可选的

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

现在已经有许多现场的实现日志的方案了。

日志

引入设计模式的概念

设计模式是软件工程中用于解决常见设计问题的可重用解决方案。它们提供了一种标准化的方法来处理特定类型的问题,帮助开发者设计出更灵活、可维护和可扩展的软件系统。设计模式并不是具体的代码,而是描述如何组织代码的模板或蓝图。

下面在代码中聊,模板方法模式

cpp 复制代码
   class Logstrategy // 是基类     
    {
    public:
        ~Logstrategy() = default; // 编译器自动生成该析构函数
        virtual void syncloy(const string &message) = 0;//纯虚继承
    };

    class ConsoleLogstrateg : public Logstrategy // 屏幕打印派生类
    {
    public:
        ConsoleLogstrateg()
        {
        }

        void syncloy(const string &message) override //检测继承是否是继承的基类
        {
            LockGuard lockguard(_mutex);
            cout << message << grep;
        }

        ~ConsoleLogstrateg()
        {
        }
    private:
        Mutex _mutex;
    };

    class FileLogstrateg : public Logstrategy // 指定文件打印的派生类
    {
    public:
        FileLogstrateg(string defaultpath, string defaultfile)
            : _path(defaultpath), _file(defaultfile)
        {
            LockGuard lockguard(_mutex);
            if(filesystem ::exists(_path)) // 判断路径存不存在 ,为真不存在retuen,创建新路径
            {
                return;
            }
            try
            {
                filesystem ::create_directories(_path); // 创建新路径
            }
            catch (const filesystem ::filesystem_error &e) // 没创建成功捕捉异常
            {
                cerr << e.what() << "\n";
            }
        }

        void syncloy(const string &message) override
        {
            LockGuard lockguard(_mutex);
            string filename = _path + _file;  // 文件名
            ofstream out(filename, ios::app); // 追加写入的方式打开文件
            if (!out.is_open())
            {
                return;
            }

            out << message << grep;
            out.close();
        }

        ~FileLogstrateg()
        {
        }
    private:
        string _path;
        string _file;
        Mutex _mutex;
    };

我们的日志想设计两种刷新方法,一种是往控制台中刷新日志,一种是往文件中刷新日志。这里采用模板模式的设计模式。基类就写好模板方法,两个子类一个往控制台中刷一个往文件中刷,两个子类继承模板方法去实现具体的方法。

往控制台中打的子类,在实现打印逻辑的函数中直接用输入输出流打印到控制台就ok了。往文件中打的子类,先判断打印日志存储的路径存不存在,不存在就创建新路径。然后在实现打印逻辑中,创建文件以追加写入的方式打开文件,把内容写入文件中。


Log.hpp

cpp 复制代码
   class Logger    //日志
    {
    public:
        Logger()
        {
            EnableFileLogstrateg();
        }
        void EnableConsoleLogstrateg()  //选择刷新策略 控制台刷新
        {
            _fflush_strategy = make_unique<ConsoleLogstrateg>(); // c++14语法通过指针创建并初始化指针对象
        }

        void EnableFileLogstrateg()    //选择刷新策略 文件刷新
        {
            _fflush_strategy = make_unique<FileLogstrateg>(defaultpath + "/", defaultfile);
        }

        class LogMessage    //内部类。表示一条日志
        {
        public:
            LogMessage(LogLevel& level, 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)
            {
                stringstream ss;
                ss << "[" << _curr_time << "]"
                   << "[" << Leve12str(_level) << "]"
                   << "[" << _pid << "]"
                   << "[" << _src_name << "]"
                   << "[" << _line_number << "]"
                   << "-";

                _loginfo = ss.str();    //stringstream流中的信息放到_loginfo中
            }

            template <class T>
            LogMessage &operator<<(const T &info) // 重载
            {
                stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }

            ~LogMessage()
            {
                if (_logger._fflush_strategy)
                {
                    _logger._fflush_strategy->syncloy(_loginfo);
                }
            }

        private:
            string _curr_time;  //时间
            LogLevel _level;    //等级
            pid_t _pid;         
            string _src_name;   //文件
            int _line_number;   //行号
            string _loginfo; // 合并后的完整信息
            Logger &_logger;
        };

        //不写&,故意创建临时对象
        LogMessage operator()(LogLevel level, std::string name, int line) //仿函数调用的时候创建临时对象,对单个日志进行构造
        {
            return LogMessage(level, name, line, *this);
        }

        ~Logger()
        {
        }

    private:
        unique_ptr<Logstrategy> _fflush_strategy; // 智能指针创建指针
    };

Logger类是整个日志,LogMessage这个内嵌类是表示一条日志。

cpp 复制代码
      Logger()
        {
            EnableFileLogstrateg();
        }
        void EnableConsoleLogstrateg()  //选择刷新策略 控制台刷新
        {
            _fflush_strategy = make_unique<ConsoleLogstrateg>(); // c++14语法通过指针创建并初始化指针对象
        }

        void EnableFileLogstrateg()    //选择刷新策略 文件刷新
        {
            _fflush_strategy = make_unique<FileLogstrateg>(defaultpath + "/", defaultfile);
        }

上面一段代码是在构造的这个日志的时候选择刷新策略。私有成员变量里我们用智能指针创建了一个智能指针对象 _fflush_strategy,两种创新策略方法(控制台刷新,文件刷新)有了智能指针对象就能用make_uniqe实例化对象了。构造日志Logger的时候要选一种默认的构造方法。

cpp 复制代码
class LogMessage    //内部类。表示一条日志
        {
        public:
            LogMessage(LogLevel& level, 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)
            {
                stringstream ss;
                ss << "[" << _curr_time << "]"
                   << "[" << Leve12str(_level) << "]"
                   << "[" << _pid << "]"
                   << "[" << _src_name << "]"
                   << "[" << _line_number << "]"
                   << "-";

                _loginfo = ss.str();    //stringstream流中的信息放到_loginfo中
            }

            template <class T>
            LogMessage &operator<<(const T &info) // 重载
            {
                stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }

            ~LogMessage()
            {
                if (_logger._fflush_strategy)
                {
                    _logger._fflush_strategy->syncloy(_loginfo);
                }
            }

        private:
            string _curr_time;  //时间
            LogLevel _level;    //等级
            pid_t _pid;         
            string _src_name;   //文件
            int _line_number;   //行号
            string _loginfo; // 合并后的完整信息
            Logger &_logger;
        };

这个内嵌类是一条日志,日志要包含,时间、等级、所属文件,行号,合并后的完整信息。构造包含这些信息。

获取时间

cpp 复制代码
    string GetTimestamp() // 获取时间
    {
        time_t curr = time(nullptr);
        struct tm curr_tm;
        localtime_r(&curr, &curr_tm);
        char timebuffer[128];
        snprintf(timebuffer, sizeof(timebuffer), "%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 string(timebuffer);
    }

其实就是个写入操作,把用时间戳获取的时间写入创建的buffer中,最后返回这个buffer。

错误等级

cpp 复制代码
   enum class LogLevel // 枚举错误类型
    {
        DEBUG,
        INFO,
        WARNINC,
        ERROR,
        FATAL
    };

    string Leve12str(const LogLevel &level) // 为避免枚举是整数
    {
        switch (level)
        {
        case LogLevel ::DEBUG:
            return "DEBUG";
        case LogLevel ::INFO:
            return "INFO";
        case LogLevel ::FATAL:
            return "FATAL";
        case LogLevel ::WARNINC:
            return "WARNINC";
        case LogLevel ::ERROR:
            return "ERROR";
        }
    }

一个枚举类型,把要写的错误类型进行枚举,为了避免枚举的是整数,所以要写这么个函数,其实可以理解为用switch语句再套一层输出为字符串。

其他信息的构造就不一一阐述了。

cpp 复制代码
 _loginfo = ss.str();    //stringstream流中的信息放到_loginfo中

这里我们把stringstream获取的日志信息都放入_loginfo(合并信息)中

cpp 复制代码
   template <class T>
   LogMessage &operator<<(const T &info) // 重载
   {
       stringstream ss;
       ss << info;
       _loginfo += ss.str();
       return *this;
    }

为了我们命令写入的信息也要写入合并信息中,这里重载一个函数,<<就把我们自己写入的信息也写入_loginfo(合并信息)中。


下面这里是一个非常妙的设计

RAII+l临时对象的自动析构的经典玩法

cpp 复制代码
 LogMessage operator()(LogLevel level, std::string name, int line) //仿函数调用的时候创建临时对象,对单个日志进行构造
 {
     return LogMessage(level, name, line, *this);
 }
//构造LogMessage的时候参数中有一个Logger &logger
LogMessage(LogLevel& level, 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)

这是一个仿函数,创建LogMessage对象的时候,创建的是临时对象返回临时对象。

cpp 复制代码
   ~LogMessage()
   {
   		if (_logger._fflush_strategy)
           {
                _logger._fflush_strategy->syncloy(_loginfo);
           }
   }

创建的LogMessage析构的时候,这时临时对象刷新完然后析构,让这个日志只活在一行代码内。这时候就不用手动刷新了,临时对象只要语句一结束一析构,就会自动刷新。


cpp 复制代码
  // 全局日志对象
   Logger logger;

   // 使用宏,简化用户操作,获取文件名和行号
   #define LOG(level) logger(level, __FILE__, __LINE__)
   #define Enable_Console_Log_Strategy() logger.EnableConsoleLogstrateg()
   #define Enable_File_Log_Strategy() logger.EnableFileLogstrateg()

用宏简化日志系统的调用方式,让使用者写日志时更简单、更自然。

日志源码:源码

相关推荐
im_AMBER6 分钟前
Leetcode 01 java
java·学习·leetcode
鸠摩智首席音效师9 分钟前
Linux Bash 中 $? 的详细用法
linux·chrome·bash
白毛大侠9 分钟前
解决 Linux Bash 脚本因换行符问题导致的 “bash^M: No such file or directory“ 错误
linux·运维·bash
说码解字19 分钟前
Kotlin 协程
java·前端·kotlin
laowangpython35 分钟前
高频Java面试题深度拆解:String/StringBuilder/StringBuffer三剑客对决(万字长文预警)
java·开发语言·其他
一眼青苔37 分钟前
如何查看 Ubuntu开机是否需要密码
linux·运维·ubuntu
YOYO--小天1 小时前
RK3588查看板卡系统信息
linux·嵌入式硬件
一个学Java小白1 小时前
ARM-Linux 完全入门
linux·运维·arm开发
程序员老王wd1 小时前
多线程下如何保证事务的一致性
java