C++项目实战——sylar服务器框架:日志系统

266 阅读5分钟

什么是日志系统

日志系统用于记录和管理应用程序或系统在运行过程中产生的各种信息。 它的主要功能是跟踪和记录程序的行为、状态变化、错误信息以及其他有用的数据,以便开发者、系统管理员或运维人员能够分析、调试和优化系统。日志系统在开发、测试、部署和维护的各个阶段都有重要作用。

日志系统的简要组成

(一) 日志级别(LogLevel)

定义不同的日志级别,如 DEBUG、INFO、WARN、ERROR 和 FATAL,以便于根据重要性过滤和记录日志。

(二) 日志事件(LogEvent)

封装日志信息,包括日志内容、时间戳、文件名、行号、线程 ID 和协程 ID 等,方便记录和传递。

(三) 日志包装器(LogEventWrap)

用于 RAII(资源获取即初始化)管理日志事件的生命周期,确保在离开作用域时自动执行日志写入。

(四) 日志格式器(LogFormatter)

负责将日志事件格式化为字符串,支持通过模式定义格式,便于灵活调整输出样式。

(五) 日志输出地(LogAppender)

负责将日志输出到不同的目标,如控制台或文件,支持多种输出方式。可以有多种具体实现,如 StdoutLogAppenderFileLogAppender

(六) 日志器(Logger)

核心组件,负责管理日志级别、输出格式和附加的输出地(Appender)。提供各种记录日志的方法,如 debug、info、warn、error 和 fatal。

(七) 日志管理器(LoggerManager)

用于管理多个日志器的生命周期和配置,提供获取和初始化日志器的功能。

(八) 单例模式(Singleton)

通常通过单例模式管理日志管理器,确保全局只有一个实例,方便访问和管理。

知识点

(一)类内 static 函数

// 日志级别
// 日志级别
class LogLevel
{
public:
    enum Level
    {
        UNKNOW = 0,
        DEBUG = 1,
        INFO = 2,
        WARN = 3,
        ERROR = 4,
        FATAL = 5
    };

    // 将枚举值转化为对应的字符串表示
    static const char* ToString(LogLevel::Level level);
    static LogLevel::Level FromString(const std::string& str);
};
  • 将 ToString 定义为 static 的原因: 日志级别是全局的、与具体对象无关的数据。LogLevel::ToString() 的唯一任务是将枚举值转换为字符串,这个操作不依赖于 LogLevel 类的任何实例属性。因此,使用静态方法可以在不需要创建 LogLevel 对象的情况下直接调用它。

(二)宏的使用

#define SYLAR_LOG_FMT_LEVEL(logger, level, fmt, ...) \
    if(logger->getLevel() <= level) \
        sylar::LogEventWrap(sylar::LogEvent::ptr(new sylar::LogEvent(logger, level, \
            __FILE__, __LINE__, 0, sylar::GetThreadId(), sylar::GetFiberId(), \
            time(0)))).getEvent()->format(fmt, __VA_ARGS__) // __VA_ARGS__是用于处理宏定义中的可变参数的预处理器标识符

#define SYLAR_LOG_FMT_DEBUG(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::DEBUG, fmt, __VA_ARGS__)
#define SYLAR_LOG_FMT_INFO(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::INFO, fmt, __VA_ARGS__)
#define SYLAR_LOG_FMT_WARN(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::WARN, fmt, __VA_ARGS__)
#define SYLAR_LOG_FMT_ERROR(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::ERROR, fmt, __VA_ARGS__)
#define SYLAR_LOG_FMT_FATAL(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::FATAL, fmt, __VA_ARGS__)
  • 在宏定义中,使用反斜杠 \ 来明确表明该行与下一行连接。
  • 虽然 C++ 允许在某些情况下直接换行,但在特定场景下(如宏定义、字符串字面量)仍需使用反斜杠或其他方式来保证代码的正确性。

(三)RAII(资源获取即初始化)原则

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是一种编程习惯,尤其在 C++ 中广泛应用。它的核心理念是将资源的生命周期与对象的生命周期绑定在一起,从而简化资源管理并减少内存泄漏或资源泄露的风险。

RAII 的主要特点:

  1. 资源管理

    资源(如内存、文件句柄、网络连接等)在对象的构造函数中获取,而在对象的析构函数中释放。这意味着只要对象存在,资源就会保持有效;一旦对象被销毁,资源也会自动释放。

  2. 异常安全

    RAII 确保即使在发生异常的情况下,资源也能得到正确释放。由于析构函数在对象生命周期结束时总会被调用,使用 RAII 可以避免遗漏资源释放的问题。

  3. 简化代码

    RAII 减少了手动管理资源的复杂性,开发者不再需要显式调用释放资源的函数,降低了出错的可能性。

class LogEventWrap
{
public:
    LogEventWrap(LogEvent::ptr e);
    ~LogEventWrap();

    LogEvent::ptr getEvent() const {return m_event;}
    std::stringstream& getSS();
private:
    LogEvent::ptr m_event;
};

LogEventWrap::~LogEventWrap()
{
    m_event->getLogger()->log(m_event->getLevel(), m_event);
}
  • 析构函数:通过在 LogEventWrap 对象销毁时自动写入日志,实现了自动管理日志事件的生命周期,减少手动管理资源的复杂性,确保日志记录的完整性和异常安全性。这种设计可以帮助开发者更专注于业务逻辑,而不必关心何时提交日志,避免错误和遗漏。
  • 通过下面的代码使用这个类:
#define SYLAR_LOG_LEVEL(logger, level) \
    if(logger->getLevel() <= level) \
        sylar::LogEventWrap(sylar::LogEvent::ptr(new sylar::LogEvent(logger, level, \
            __FILE__, __LINE__, 0, sylar::GetThreadId(), \
            sylar::GetFiberId(), time(0)))).getSS()

#define SYLAR_LOG_DEBUG(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::DEBUG)
#define SYLAR_LOG_INFO(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::INFO)
#define SYLAR_LOG_WARN(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::WARN)
#define SYLAR_LOG_ERROR(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::ERROR)
#define SYLAR_LOG_FATAL(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::FATAL)

SYLAR_LOG_INFO(logger) << "test macro";
SYLAR_LOG_ERROR(logger) << "test macro error";

(四)单例模式

#ifndef __SYLAR_SINGLETON_H__
#define __SYLAR_SINGLETON_H__

#include <memory>

namespace sylar
{
    template<class T, class X = void, int N = 0>
    class Singleton
    {
    public:
        static T* GetInstance()
        {
            static T v;
            return &v;
        }
    };


    template<class T, class X = void, int N = 0>
    class SingletonPtr
    {
    public:
        static std::shared_ptr<T> GetInstance()
        {
            static std::shared_ptr<T> v(new T);
                return v;
        }
    };
}

#endif

通过下面的方法使用:

typedef sylar::Singleton<LoggerManager> LoggerMgr;


auto l = sylar::LoggerMgr::GetInstance();

单例模式详细介绍见:设计模式---单例模式

(五)std::enable_shared_from_this

std::enable_shared_from_this 是 C++ 标准库中的一个工具,用于允许类的实例安全地生成指向自身的 std::shared_ptr。这在某些情况下非常有用,特别是当一个对象需要在其成员函数中创建指向自己的共享指针时。

要使用 std::enable_shared_from_this,你需要使类继承自它,并且在使用之前确保对象是通过 std::shared_ptr 创建的。

class Logger : public std::enable_shared_from_this<Logger>
{
//...
}