> 文章列表 > 从零开始实现一个C++高性能服务器框架----日志模块

从零开始实现一个C++高性能服务器框架----日志模块

从零开始实现一个C++高性能服务器框架----日志模块

此项目是根据sylar框架实现,是从零开始重写sylar,也是对sylar丰富与完善
项目地址:https://gitee.com/lzhiqiang1999/server-framework

简介

项目介绍:实现了一个基于协程的服务器框架,支持多线程、多协程协同调度;支持以异步处理的方式提高服务器性能;封装了网络相关的模块,包括socket、http、servlet等,支持快速搭建HTTP服务器或WebSokcet服务器。
详细内容日志模块,使用宏实现流式输出,支持同步日志与异步日志、自定义日志格式、日志级别、多日志分离等功能。线程模块,封装pthread相关方法,封装常用的锁包括(信号量,读写锁,自旋锁等)。IO协程调度模块,基于ucontext_t实现非对称协程模型,以线程池的方式实现多线程,多协程协同调度,同时依赖epoll实现了事件监听机制。定时器模块,使用最小堆管理定时器,配合IO协程调度模块可以完成基于协程的定时任务调度。hook模块,将同步的系统调用封装成异步操作(accept, recv, send等),配合IO协程调度能够极大的提升服务器性能。Http模块,封装了sokcet常用方法,支持http协议解析,客户端实现连接池发送请求,服务器端实现servlet模式处理客户端请求,支持单Reator多线程,多Reator多线程模式的服务器。

日志模块

在这里插入图片描述

1. 主要功能

  • 支持不同日志级别
  • 可以自由的控制日志输出的位置(目前包括输出到控制台,文件)
  • 支持自定义日志格式
  • 设置了一系列工具宏,实现流式输出
  • 目前还是同步日志,后期再添加异步日志

2. 功能演示

  • 支持不同的日志级别,控制日志输出位置,自定义日志格式
Logger::ptr logger(new Logger(Logger::DEBUG, "root"));// 设置日志输出到终端的格式
LogFormatter::ptr std_formatter(new LogFormatter("%d{%Y-%m-%d %H:%M:%S}%T%m%T%n"));
StdLoggerAppender::ptr std_appeder(new StdLoggerAppender(Logger::INFO, std_formater));// 设置日志输出到文件的格式
LogFormatter::ptr file_formatter(new LogFormatter("%d%T%f%T%m%T%n"));
FileLoggerAppender::ptr file_appeder(new FileLoggerAppender("./logs.log", Logger::ERROR, file_formatter));// 将输出地添加到日志器
logger->addAppender(std_appender);
logger->addAppender(file_appender);// 日志事件
LogEvent::ptr event(new LogEvent(logger, Logger::DEBUG, __FILE__, __LINE__, 0, getThreadId(), getFiberId(), time(0), Thread::GetName()));
event->getSS() << "这是一条日志";// 输出日志,只会按照设置的日志级别和日志格式输出
logger->log(Logger::DEBUG, event)
  • 使用,流式输出
Logger::ptr logger = LOG_NAME("system");
LOG_DEBUG(logger) << "这是一串日志";

3. 模块介绍

3.1 LogLevel

  • 日志级别,主要有以下几类
    enum Level
    {UNKNOW = 0,DEBUG = 1,INFO = 2,WARN = 3,ERROR = 4,FATAL = 5
    };
    

3.2 LogEvent

  • 日志事件,封装了所有需要输出的日志信息,具体包括以下信息
    const char* m_file = nullptr;		/// 文件名
    int32_t m_line = 0;					/// 行号
    uint32_t m_elapse = 0;				/// 程序启动开始到现在的毫秒数
    uint32_t m_threadId = 0;			/// 线程ID
    uint32_t m_fiberId = 0;				/// 协程ID
    uint64_t m_time = 0;				/// 时间戳
    std::string m_threadName;			/// 线程名称
    std::stringstream m_ss;				/// 日志内容流
    std::shared_ptr<Logger> m_logger;	/// 日志器
    LogLevel::Level m_level;			/// 日志等级
    

3.3 LogFormatter

  • 日志格式化,负责解析日志格式模板,完成格式化输出。提供format方法对LogEvent对象进行格式化并返回对应的字符串或流
  • 日志格式化模板
    %m 消息
    %p 日志级别
    %r 累计毫秒数
    %c 日志名称
    %t 线程id
    %n 换行
    %d 时间
    %f 文件名
    %l 行号
    %T 制表符
    %F 协程id
    %N 线程名称默认格式 "%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
    
  • FormatItem
    • 负责将解析后的模板,按照不同类型构建Item类,放入vector<FormatItem::ptr> m_items,最后再统一输出日志
    %m		MessageFormatItem			消息			
    %p 		LevelFormatItem				日志级别
    %r		ElapseFormatItem 			累计毫秒数
    %c		NameFormatItem 				日志名称
    %t		ThreadIdFormatItem 			线程id
    %n		NewLineFormatItem 			换行
    %d		DateTimeFormatItem 			时间
    %f		FilenameFormatItem 			文件名
    %l		LineFormatItem 				行号
    %T		TabFormatItem 				制表符
    %F		FiberIdFormatItem 			协程id
    %N		ThreadNameFormatItem 		线程名称
    

3.4 LogAppender

  • 日志输出,负责日志的输出,是一个基类,可以针对不同的需求拓展出新的日志输出类,这里主要实现了StdLoggerAppender输出到终端,FileLoggerAppender输出到文件
	class LogAppender{public:typedef SpinLock MutexType;						//自旋锁typedef std::shared_ptr<LogAppender> ptr;LogAppender();//默认日志级别:DEBUG//默认格式:"%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"LogAppender(LogLevel::Level level, LogFormatter::ptr formater);virtual void log(LogLevel::Level level, LogEvent::ptr event) = 0;//get set方法virtual std::string toYamlString() = 0;protected:LogLevel::Level m_level = LogLevel::DEBUG;	/// 默认DEBUGLogFormatter::ptr m_formater;				/// 格式化输出MutexType m_mutex;							/// 锁 };	
  • 细节

    • 因为整个项目是基于多线程多协程开发,所以势必会涉及到线程安全问题。这里使用了自旋锁,因为锁住的代码执行时间短,自旋锁空转的时间比较短。
  • StdLoggerAppender

    • 输出到终端
    	/* @brief 输出到控制台的*/
    class StdoutLogAppender : public LogAppender
    {
    public:typedef std::shared_ptr<StdoutLogAppender> ptr;StdoutLogAppender();//默认日志级别:DEBUG//默认格式:"%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"StdoutLogAppender(LogLevel::Level level, LogFormatter::ptr formater)void log(LogLevel::Level level, LogEvent::ptr event) override;std::string toYamlString() override;
    private:
    };
    
  • FileLoggerAppender

    • 输出到文件
    class FileLogAppender : public LogAppender
    {
    public:typedef std::shared_ptr<FileLogAppender> ptr;FileLogAppender(const std::string& filename);//默认日志级别:DEBUG//默认格式:"%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"FileLogAppender(const std::string& filename, LogLevel::Level level, LogFormatter::ptr formater);~FileLogAppender();//重新打开文件,文件打开成功,返回truebool reopen();void log(LogLevel::Level level, LogEvent::ptr event) override;std::string toYamlString() override;
    private:std::string m_filename;std::ofstream m_filestream;/// 上次重新打开时间uint64_t m_lastTime = 0;
    };
    

3.5 Logger

  • 日志器,可以设置日志级别,包含多个LoggerAppendervector<LogAppender::ptr> m_appenders
class Logger
{
public:typedef SpinLock MutexType;Logger(LogLevel::Level level, const std::string& name = "root");Logger(const std::string& name = "root");
public:typedef std::shared_ptr<Logger> ptr;void log(LogLevel::Level level, LogEvent::ptr);void debug(LogEvent::ptr event);void info(LogEvent::ptr event);void warn(LogEvent::ptr event);void error(LogEvent::ptr event);void fatal(LogEvent::ptr event);void addAppender(LogAppender::ptr appender);void delAppender(LogAppender::ptr appender);void clearAppenders();//get set方法std::string toYamlString();private:std::string m_name;							//日志名称LogLevel::Level m_level;					//日志级别std::vector<LogAppender::ptr> m_appenders;	//Appender集合MutexType m_mutex;};
  • 日志事件由log方法输出,log方法首先判断日志级别是否达到本Logger的级别要求,是则将日志传给各个LogAppender进行输出,否则抛弃这条日志。

3.6 LoggerManager

  • 日志管理。管理所有的日志器map<std::string, Logger::ptr> m_loggers
class LoggerManager
{
public:typedef SpinLock MutexType;Logger::ptr getLogger(const std::string & name);Logger::ptr getRoot() {return m_root}LoggerManager();std::string toYamlString();
private:std::map<std::string, Logger::ptr> m_loggers;	/// 所有的日志器Logger::ptr m_root;								/// 默认有一个根日志器MutexType m_mutex;
};
  • 使用单例模式,这里实现了一个较为通用的单例类Singleton,关于c++单例模式,可以参考这篇文章
template<class T>
class Singleton
{
public:/* @brief 返回单例裸指针*/static T* GetInstance() {static T v;return &v;}	
};template<class T>
class SingletonPtr {
public:/* @brief 返回单例智能指针*/static std::shared_ptr<T> GetInstance() {static std::shared_ptr<T> v(new T);return v;}
};

这样,我们就可以构建出一个单例LoggerManager

typedef johnsonli::Singleton<LoggerManager> LoggerMgr;
Logger logger = LoggerMgr::GetInstance()->getLogger(name);
  • 此外,还提供了更加方便的来创建日志器
#define LOG_ROOT() johnsonli::LoggerMgr::GetInstance()->getRoot()
#define LOG_NAME(name) johnsonli::LoggerMgr::GetInstance()->getLogger(name)

3.7 日志输出宏

  • 定义了一些方便使用的宏,能够实现流式输出
#define LOG_LEVEL(logger, level) \\if(logger->getLevel() <= level) \\johnsonli::LogEventWrap(johnsonli::LogEvent::ptr(new johnsonli::LogEvent(logger, level, \\__FILE__, __LINE__, 0, johnsonli::getThreadId(),\\johnsonli::getFiberId(), time(0), johnsonli::Thread::GetName()))).getSS()#define LOG_DEBUG(logger) LOG_LEVEL(logger, johnsonli::LogLevel::DEBUG)
#define LOG_INFO(logger) LOG_LEVEL(logger, johnsonli::LogLevel::INFO)
#define LOG_WARN(logger) LOG_LEVEL(logger, johnsonli::LogLevel::WARN)
#define LOG_ERROR(logger) LOG_LEVEL(logger, johnsonli::LogLevel::ERROR)
#define LOG_FATAL(logger) LOG_LEVEL(logger, johnsonli::LogLevel::FATAL)
  • 这里用到了一个LogEventWrap类, 这是对日志事件的一层封装,实现在~LogEventWrap()析构函数中完成日志输出
    class LogEventWrap
    {
    public:LogEventWrap(LogEvent::ptr event);~LogEventWrap(){//在析构的时候完成日志输出if (m_event){m_event->getLogger()->log(m_event->getLevel(), m_event);}}LogEvent::ptr getEvent() const { return m_event; }std::stringstream& getSS() { return m_event->getSS(); }private:LogEvent::ptr m_event;
    };
    
  • 最后,就可以使用以下方式完成日志打印
Logger::ptr logger = LOG_NAME("system");
LOG_DEBUG(logger) << "这是一串日志";