深入了解linux系统—— 日志

日志

在之前写代码的过程中,测试代码都是之间像显示器上输出内容;

当多线程像显示器文件输出时,由于没有做任何的防护,就有可能导致多线程输出信息混在一起,不方便观察。

而计算机中的日志记录系统和软件运行中发生事件的文件,作用就是:监控运行状态,记录异常信息,帮助快速定位问题并支持程序员进行问题修复。

是系统维护、故障排查和安全管理的重要工具。

简单来说:日志就像生活中的日记一样,记录程序运行时运行状态、异常信息等等。

对于一个合格的日志,要具有以下指标:

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

文件名、行号、进程/线程id

最主要的就是时间戳、日志等级和日志内容。

一般来说,日志等级可以分为:

​ DEBUG:调试信息

​ INFO:正常输出

​ WARNING:告警信息

​ ERROR:错误信息(能够运行结束)

​ FATAL:错误(不能运行:打开文件失败等等)

日志有现成的解决方案,spdlogglogBoost.Log等等;

这里自定义实现一个日志(采用设计模式 - 策略模式)。

刷新策略

要自定义实现一个日志,这里首先来实现一种刷新策略;

假设现在存在一条日志信息,可以刷新到显示器文件中(显示器策略)、也可以刷新到指定文件中(文件策略)

这里,我们就可以设计一个基类:logflush,其中存在一个虚函数flush

对于一种刷新策略,就要继承基类logflush并重写flush方法实现自己的刷新策略。

cpp 复制代码
    class logflush
    {
        virtual void flush(std::string massage) = 0;
        ~logflush() = delete;
    };

显示器刷新策略

向显示器文件中刷新,这里直接使用std::cout即可

注意:日志可以被多线程使用,显示器文件就是临界资源,要对临界区进行加锁 (这里就使用之间封装的Mutexlockgroup

cpp 复制代码
    // 显示器刷新
    class displayflush : public logflush
    {
        void flush(std::string massage) override
        {
            lockgroup(_mutex);
            std::cout << massage << std::endl;
        }

    private:
        Mutex _mutex;
    };

文件刷新策略

向文件中刷新,首先要先打开这个文件,我们就要知道该文件的路径、文件名。

要打开一个文件、如果该文件不存在,调用open时可以新建;但是,如果路径不存在,我们这里调用就会出错。

所以,我们首先要做的是:判断文件路径是否存在,如果该路径不存在,就要新建。

这里可以使用std::filesystem:exists来判断一个路径是否存在(路径存在返回true,不存在返回false

使用std::filesystem::create_directories来创建一个路径。

cpp 复制代码
    static std::string default_path = "./log";
    static std::string default_name = "log.log";
    const std::string gsep = "\r\n";
    class fileflush : public logflush
    {
    public:
        fileflush(const std::string &path = default_path, const std::string &name = default_name)
            : _path(path), _name(name)
        {
            if (std::filesystem::exists(path))
            {
                return;
            }
            // 路径不存在,创建
            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << gsep;
            }
        }
    private:
        std::string _path;
        std::string _name;
        std::string _pathname;
        Mutex _mutex;
    };

然后就是重写flush方法:

在打开目标文件之前,要对文件路径和名称进行合并,生成文件绝对路径。

要打开目标文件(以追加方式打开,文件不存在就创建)(这里打开文件可以使用:std::ofstream out(_pathname, std::ios::app);C++17支持,std::ios::app表示以追加方式打开文件)

然后就是将日志信息输出到目标文件中,为了方便输出,这里定义一个结尾符:const std::string gsep = "\r\n";

最后关闭文件。

为了保证线程安全,要进行加锁

cpp 复制代码
        void flush(std::string massage) override
        {
            lockgroup lgp(_mutex);
            _pathname = _path + (_path.back() == '/' ? ' ' : '/') + _name;
            std::ofstream out(_pathname, std::ios::app);
            if (!out.is_open())
            {
                // 打开文件失败
                return;
            }
            out << massage << gsep;
        }

日志信息

1. 构建日志

有了日志刷新策略,现在来实现日志log

要实现日志,首先就要有上述的刷新策略,这里默认使用显示器刷新策略:

cpp 复制代码
    class Log
    {
        public:
        Log()
        {
            _log = std::make_unique<displayflush>();
        }
        void EnableDisplayFlush()
        {
            _log = std::make_unique<displayflush>();
        }
        void EnableFileFlush()
        {
            _log = std::make_unique<fileflush>();
        }
        private:
        std::unique_ptr<logflush> _logflush;
    }

有了上述刷新策略,现在来看日志信息:

复制代码
[2025-8-24 22:42:35] [DEBUG] [641189] [test.cc] [12] log.txt 2015-8-24
[2025-8-24 22:42:35] [DEBUG] [641189] [test.cc] [13] hello

这里预期的日志信息如上,在一条日志中有存在时间,日志等级,进程pid,文件名,行号,信息等。

所以,我们就要实现获取时间的接口函数GetTime,以及日志等级enum class Level

这里,Level枚举类型默认输出是整型,我们想要以DEBUGINFO这样的形式输出,就需要提供一个方法根据日志等级获取相对对应字符串。

cpp 复制代码
    std::string GetTime()
    {
        time_t tm = time(nullptr);
        struct tm curr;
        localtime_r(&tm, &curr);
        std::stringstream ss;
        ss << curr.tm_year + 1900 << "-"
           << curr.tm_mon + 1 << "-"
           << curr.tm_mday << " "
           << curr.tm_hour << ":"
           << curr.tm_min << ":"
           << curr.tm_sec;
        return ss.str();
    }
    enum class Level
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };
    std::string GetLevel(Level level)
    {
        switch (level)
        {
        case Level::DEBUG:
            return "DEBUG";
        case Level::INFO:
            return "INFO";
        case Level::WARNING:
            return "WARNING";
        case Level::ERROR:
            return "ERROR";
        case Level::FATAL:
            return "FATAL";
        default:
            return "UNKONW";
        }
    }

有了上述这些内容,现在来实现一条日志信息logmassage将其设计成Log内部类

一条日志,要具有 时间、日志等级、进程id、文件名、行号,日志信息;

cpp 复制代码
        class Logmassage
        {
        public:
        private:
            std::string _time;       // 日志时间
            Level _level;            // 日志等级
            pid_t _pid;              // 进程id
            std::string _filename;   // 文件名
            int _line;               // 行号
            std::string _logmassage; // 完整的日志信息
            Log *_log;               // log指针,方便刷新日志信息
        };

这里像日志等级,文件名、行号等不能自行获取的,就要通过构造函数参数传递进来;

然后根据这些信息,构建出来完整的日志信息。

构建完整的日志信息,可以使用C语言中的sprintf/snprintf来实现;

这里使用C++中的stringstream类。

cpp 复制代码
        class Logmassage
        {
        public:
            Logmassage(const std::string &time, Level level, const std::string &filename, int line, Log *plog)
                : _time(time), _level(level), _pid(getpid()), _filename(filename), _line(line), _log(log)
            {
                std::stringstream ss;
                ss << '[' << _time << ']'
                   << '[' << GetLevel(_level) << ']' /*枚举类型,默认是整型*/
                   << '[' << _pid << ']'
                   << '[' << _filename << ']'
                   << '[' << _line << "] : ";
                _logmassage = ss.str();
            }
        private:
            std::string _time;       // 日志时间
            Level _level;            // 日志等级
            pid_t _pid;              // 进程id
            std::string _filename;   // 文件名
            int _line;               // 行号
            std::string _logmassage; // 完整的日志信息
            Log *_log;               // log指针,方便刷新日志信息
        };

这里在Logmassage类中存在Log* _log的指针,方便进行日志信息刷新

2. 输入日志信息

上述已经完成了整个日志的框架,但是还缺少信息;

这里想要实现的使用日志的方式,就像cout <<这样使用<<来输入日志信息,所以就要实现operator<<方法;

并且,可以连续使用多个<<,在使用operator时,要将返回值设置成Logmassage&

cpp 复制代码
            template <typename T>
            Logmassage &operator<<(const T &data)
            {
                std::stringstream ss;
                ss << data;
                _logmassage += ss.str();
                return *this;
            }

最后,为了方便使用在Loamassage析构方法中,刷新该日志信息;

cpp 复制代码
            ~Logmassage()
            {
                if (_log->_logflush)
                {
                    _log->_logflush->flush(_logmassage);
                }
            }

3. 使用日志

到现在,就已经将日志大概实现了出来;

但是,按照现在实现的日志,我们使用起来存在问题:

创建Logmassage就需要存在一个已经有的Log指针,而我们使用日志就要先创建Log对象。

所以这里就要实现一个仿函数,在调用Log()时,用来构建Logmassage对象并返回。

cpp 复制代码
        Logmassage operator()(const std::string &time, Level level, const std::string &filename, int line)
        {
            return Logmassage(time, level, filename, line, this);
        }

但是,就算实现了仿函数,我们要使用该日志时,还是非常麻烦的,需要传递什么时间,日志等级,文件名,行号 ,有没有更加简单粗暴的,就想要只传递时间,后面跟上<< 信息就可以使用日志的?

cpp 复制代码
LOG(Level::DEBUG) << "hello log";

当然是可以实现的,时间需要调用GetTime方法;文件名和行号,我们知道宏__FILE____LINE__指的就是文件名和行号;

所以,我们就可以实现一个宏,调用是只需传递日志等级,就可以使用日志。

cpp 复制代码
    Log log;
#define LOG(level) Log(GetTime(), level, __FILE__,__LINE__)

并且将log 定义成全局的,在使用时只需使用即可。

相关推荐
叶凡要飞7 分钟前
linux安装google chrome 谷歌浏览器
linux·运维·chrome
清静诗意8 分钟前
在 Ubuntu 上通过 Docker 与 Docker Compose 部署项目的完整指南
linux·ubuntu·docker
企鹅虎29 分钟前
linux内核驱动开发视频课程
linux
专注VB编程开发20年1 小时前
vb.net编写DDE(Dynamic Data Exchange)服务器
运维·服务器·github·vb.net·dde
Clownseven1 小时前
如何用Fail2ban保护Linux服务器?防止SSH暴力破解教程
linux·服务器·ssh
源码部署21 小时前
linux内核驱动开发视频课程
linux
chaofan9802 小时前
如何用 Claude Code 搭建安全、可测、可自动化的 GitHub CI 流程?
运维·人工智能·ci/cd·ai·自动化·github·claude
无敌最俊朗@2 小时前
Linux 进程创建与控制详解
linux·运维·服务器
迎風吹頭髮2 小时前
UNIX下C语言编程与实践8-UNIX 静态库原理与创建:ar 命令的使用与静态库调用全流程
服务器·c语言·unix
张红尘2 小时前
龙蜥OS8.10配置repo源使用RPM安装Redis8.2
linux·redis·操作系统