当前位置: 首页 > article >正文

从零开始实现一个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;	/// 默认DEBUG
		LogFormatter::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();
    
    	//重新打开文件,文件打开成功,返回true
    	bool 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) << "这是一串日志";

http://www.kler.cn/a/7953.html

相关文章:

  • 随机数
  • 简易入手《SOM神经网络》的本质与原理
  • 【数学二】线性代数-线性方程组-齐次线性方程组、非齐次线性方程组
  • 【ACM出版】第四届信号处理与通信技术国际学术会议(SPCT 2024)
  • 【自用】0-1背包问题与完全背包问题的Java实现
  • 【计算机网络】UDP网络程序
  • Vue3走马灯(Carousel)
  • 3-ELK+Kafka+Filebeat 海量级日志收集 TB PB级别
  • 模板匹配及应用
  • SpringMvc中拦截器
  • 中国版ChatGPT即将来袭-国内版ChatGPT入口
  • Leetcode字符串的排列
  • Unity Animation -- 改进动画效果
  • Leetcode.559 N 叉树的最大深度
  • Debezium报错处理系列之五十七:Can‘t compare binlog filenames with different base names
  • C++从0到1实战
  • Vector - CAPL - CRC算法介绍(续)
  • Android 中封装优雅的 MediaPlayer 音频播放器,支持多个播放器
  • Ansys Zemax | 如何使用 Zernike 凹陷表面对全反射系统进行建模
  • html中开源的视频播放器插件有哪些以及官方网站和详细介绍说明
  • linux 共享内存 shmget
  • Day924.自动化测试 -系统重构实战
  • 【Linux】进程理解与学习-程序替换
  • 小白的git入门教程(二)
  • FreeRTOS学习(一)
  • 【分享】太阳能电池性能测试指标,太阳能电池IV测试软件系统