【Linux】27:日志

目录

一、日志概念

二、策略模式

[2.1 刷新到显示器](#2.1 刷新到显示器)

[2.2 刷新到文件](#2.2 刷新到文件)

三、形成完整的日志

[3.1 Log.hpp](#3.1 Log.hpp)

[3.2 Mutex.hpp](#3.2 Mutex.hpp)

[3.3 main.cc](#3.3 main.cc)

四、核心模块拆解

4.1.策略模式:日志输出策略抽象

[4.2 日志等级](#4.2 日志等级)

[4.3 时间工具函数 GetTimeStamp()](#4.3 时间工具函数 GetTimeStamp())

[4.4 主日志类 Logger](#4.4 主日志类 Logger)

[4.5 内部类 logMessage(核心:RAII 日志落盘)](#4.5 内部类 logMessage(核心:RAII 日志落盘))

[4.6 全局对象 + 宏封装](#4.6 全局对象 + 宏封装)

[4.7 行时序示例(单条 LOG)](#4.7 行时序示例(单条 LOG))


一、日志概念

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

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

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

以下几个指标是可选的

  • 文件名行号
  • 进程,线程相关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. DEBUG:测试信息
  2. INFO:常规消息
  3. WARNING:告警消息
  4. ERROR:错误消息
  5. FATAL:致命消息

日志的功能:

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

我们可以定制一个策略来让日志信息往哪里刷新。这就叫做策略模式

二、策略模式

我们可以通过定义基类的方式,来规定统一接口,然后由不同的子类,来充当一种不同的某种形式的策略,我们把这种设计模式叫做策略模式,策略模式利用的就是C++的多态特性。

策略模式的代码如下:

cpp 复制代码
#ifndef __LOG_HPP
#define __LOG_HPP
#include <iostream>
#include <filesystem>
#include <string>
#include <fstream>
#include "Mutex.hpp"

namespace LogModule
{
    using namespace MutexModule;
    const std::string gsep = "\r\n";
    // 刷新策略基类
    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 << gsep;
        }

    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 << gsep;
            out.close();
        }
        ~FileLogStrategy() {}

    private:
        std::string _path; // 日志文件所在路径
        std::string _file; // 日志文件本身
        Mutex _mutex;
    };
}

#endif

2.1 刷新到显示器

main.cc

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

using namespace LogModule;

int main()
{
    std::unique_ptr<LogStrategy> strategy = std::make_unique<ConsoleLogStrategy>();
    strategy->SyncLog("hello log1");
    strategy->SyncLog("hello log2");
    strategy->SyncLog("hello log3");

    return 0;
}

2.2 刷新到文件

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

using namespace LogModule;

int main()
{
    std::unique_ptr<LogStrategy> strategy = std::make_unique<FileLogStrategy>();
    strategy->SyncLog("hello log1");
    strategy->SyncLog("hello log2");
    strategy->SyncLog("hello log3");

    return 0;
}

三、形成完整的日志

上面策略模式我们可以选择将日志刷新到哪里,下面我们就来形成一个完整的日志。

3.1 Log.hpp

cpp 复制代码
#ifndef __LOG_HPP
#define __LOG_HPP
#include <iostream>
#include <filesystem>
#include <string>
#include <memory>
#include <unistd.h>
#include <fstream>
#include <ctime>
#include <cstdio>
#include "Mutex.hpp"

namespace LogModule
{
    using namespace MutexModule;
    const std::string gsep = "\r\n";
    // 刷新策略基类
    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 << gsep;
        }

    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 << gsep;
            out.close();
        }
        ~FileLogStrategy() {}

    private:
        std::string _path; // 日志文件所在路径
        std::string _file; // 日志文件本身
        Mutex _mutex;
    };

    // 1:形成日志等级
    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };
    std::string Level2Str(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return "UNKNOWN";
        }
    }
    // 获得时间
    std::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 timebuffer;
    }
    // 1:形成日志 2根据不同的策略,完成刷新
    class Logger
    {
    public:
        Logger()
        {
            EnableConsoleLogStrategy();
        }
        void EnableFileLogStrategy()
        {
            _fflush_strategy = std::make_unique<FileLogStrategy>();
        }
        void EnableConsoleLogStrategy()
        {
            _fflush_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._fflush_strategy)
                {
                    _logger._fflush_strategy->SyncLog(_loginfo);
                }
            }

        private:
            std::string _curr_time;
            LogLevel _level;
            pid_t _pid;
            std::string _src_name;
            int _line_number;
            std::string _loginfo; // 合并之后一条完整的信息

            Logger &_logger;
        };

        logMessage operator()(LogLevel level, std::string name, int line)
        {
            return logMessage(level, name, line, *this);
        }
        ~Logger() {}

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

    // 定义全局日志对象
    Logger logger;

// 使用宏,简化用户操作
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}

#endif

3.2 Mutex.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <pthread.h>

namespace MutexModule
{
    class Mutex
    {
    public:
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr);
        }
        void Lock()
        {
            pthread_mutex_lock(&_mutex);
        }
        void UnLock()
        {
            pthread_mutex_unlock(&_mutex);
        }
        pthread_mutex_t *Get()
        {
            return &_mutex;
        }
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex);
        }

    private:
        pthread_mutex_t _mutex;
    };
    class LockGuard
    {
    public:
        LockGuard(Mutex &mutex) : _mutex(mutex)
        {
            _mutex.Lock();
        }
        ~LockGuard()
        {
            _mutex.UnLock();
        }

    private:
        Mutex &_mutex;
    };
}

3.3 main.cc

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

using namespace LogModule;

int main()
{
    Enable_Console_Log_Strategy();
    LOG(LogLevel::DEBUG) << "hello world " << 3.14;
    LOG(LogLevel::DEBUG) << "hello world " << 3.14;
    LOG(LogLevel::DEBUG) << "hello world " << 3.14;
    LOG(LogLevel::DEBUG) << "hello world " << 3.14;
    LOG(LogLevel::DEBUG) << "hello world " << 3.14;
    LOG(LogLevel::DEBUG) << "hello world " << 3.14;
    LOG(LogLevel::DEBUG) << "hello world " << 3.14;
    Enable_File_Log_Strategy();
    LOG(LogLevel::DEBUG) << "hello file " << 3.14;
    LOG(LogLevel::DEBUG) << "hello file " << 3.14;
    LOG(LogLevel::DEBUG) << "hello file " << 3.14;
    return 0;
}

运行如下所示:

四、核心模块拆解

4.1.策略模式:日志输出策略抽象

基类 LogStrategy,定义纯虚接口 SyncLog(message):把完整字符串输出。

  1. ConsoleLogStrategy:控制台打印,内部自带互斥锁,多线程保护std::cout。
  2. FileLogStrategy:文件写日志
    • 构造:自动检查日志目录,不存在则创建目录 ./log
    • SyncLog:加锁,以追加模式打开文件 ./log/my.log,写入日志,写完关闭文件
    • 自带互斥锁,防止多线程写文件错乱

策略可以动态切换:运行时选择打印到控制台,还是写入文件。

4.2 日志等级

enum class LogLevel:DEBUG / INFO / WARNING / ERROR / FATAL,Level2Str 枚举转字符串,用于日志打印。

4.3 时间工具函数 GetTimeStamp()

获取本地时间,格式化输出 yyyy-mm-dd hh:mm:ss 时间戳,使用localtime_r线程安全版本。

4.4 主日志类 Logger

  • 成员:unique_ptr<LogStrategy>,持有当前选中的输出策略
  • 接口:
    • EnableConsoleLogStrategy():切换策略为控制台输出
    • EnableFileLogStrategy():切换策略为文件输出
    • operator():重载圆括号,返回临时对象 logMessage

4.5 内部类 logMessage(核心:RAII 日志落盘)

这是整个日志框架最关键的设计:临时对象生命周期控制日志输出

  1. 构造函数:接收日志等级、源码文件名、行号、Logger 引用; 预先拼接日志头部:[时间][等级][pid][文件名][行号]-,存入_loginfo
  2. 重载 operator<<:流式追加任意类型日志内容,拼接到_loginfo
  3. 析构函数 :对象销毁时,调用_logger所持策略的SyncLog(),把完整日志一次性输出。

调用逻辑:LOG(xxx) << msg → 创建临时logMessage对象,<<拼接内容;语句结束,临时对象析构 → 自动写日志。

4.6 全局对象 + 宏封装

复制代码
Logger logger;
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
  • __FILE__:当前源码文件名;__LINE__:当前代码行号,自动填入日志。
  • 用户使用:一行代码即可打印日志,不用手动构造对象。

4.7 行时序示例(单条 LOG)

复制代码
LOG(LogLevel::DEBUG) << "hello world " << 3.14;
  1. 宏展开:logger(LogLevel::DEBUG, __FILE__, __LINE__)
  2. logger.operator() 创建临时 logMessage 对象
  3. 构造:拼接时间、等级、pid、文件名、行号
  4. << 依次追加字符串、浮点数,拼入_loginfo
  5. 语句末尾,临时对象析构 → 调用策略 SyncLog 输出整条日志
相关推荐
时间售卖铺1 小时前
Nginx 反向代理
运维·nginx·云原生
life码农1 小时前
Nginx location 优先级详解:匹配规则、修饰符与完整代码示例
运维·nginx
别动我齐刘海1 小时前
机器人 C++ / 系统联调补强2——异常处理、Protobuf
linux·c++·人工智能·学习·算法·机器学习·机器人
Lsetea1 小时前
curl -I检查HTTPS返回405:HEAD、GET与证书验收分层排查
linux·http·https·ssl证书·curl
我命由我123451 小时前
Python 开发 - Python 虚拟环境
运维·开发语言·后端·python·http·https·java-ee
dlhto1 小时前
Windows 使用 Rufus 制作 AlmaLinux 10.2 启动 U 盘:从 ISO 下载到服务器安装
运维·服务器·windows
一直C1 小时前
嵌入式 Linux 驱动开发学习笔记:内核定时器、platform 总线与 I²C 驱动
linux·驱动开发·嵌入式硬件·学习·imx6ull
软件派2 小时前
Beam.Win 自动化办公与业务流程提效指南
运维·自动化
承渊政道2 小时前
Linux网络学习【传输层协议UDP】
linux·网络·网络协议·学习·ubuntu·udp