muduo网络库源码复现笔记(十三):base库的Logging.h

Muduo网络库简介

muduo 是一个基于 Reactor 模式的现代 C++ 网络库,作者陈硕。它采用非阻塞 IO 模型,基于事件驱动和回调,原生支持多核多线程,适合编写 Linux 服务端多线程网络应用程序。
muduo网络库的核心代码只有数千行,在网络编程技术学习的进阶阶段,muduo是一个非常值得学习的开源库。目前我也是刚刚开始学习这个网络库的源码,希望将这个学习过程记录下来。这个网络库的源码已经发布在GitHub上,可以点击这里阅读。目前Github上这份源码已经被作者用c++11重写,我学习的版本是没有使用c++11版本的。不过二者大同小异,核心思想是没有变化的。点这里可以看我的源代码,如果你对我之前的博客有兴趣,可以点击下面的连接:
muduo网络库源码复现笔记(一):base库的Timestamp.h
muduo网络库源码复现笔记(二):base库的Atomic.h
muduo网络库源码复现笔记(三):base库的Exception.h
muduo网络库源码复现笔记(四):base库的Thread.h和CurrentThread.h
muduo网络库源码复现笔记(五):base库的Mutex.h和Condition.h和CoutntDownLatch.h
muduo网络库源码复现笔记(六):base库的BlockingQueue.h和BoundedBlockingQueue.h
muduo网络库源码复现笔记(七):base库的ThreadPool.h
muduo网络库源码复现笔记(八):base库的Singleton.h
muduo网络库源码复现笔记(九):base库的ThreadLocalSingleton.h
muduo网络库源码复现笔记(十):base库的ThreadLocalSingleton.h
muduo网络库源码复现笔记(十一):base库的StringPiece.h
muduo网络库源码复现笔记(十二):base库的LogStream.h

1 Logging.h

Logging.h封装了类Logger。如笔记十二所述,封装LogStream类之后我们可以将待输出的信息缓存到LogStream的缓冲区内,而LogStream是由Logger调用的,最终由Logger将信息输出到标准输出或文件中。看一下Logger的代码:

class Logger
{
public:
	enum LogLevel
	{
		TRACE,
		DEBUG,
		INFO,
		WARN,
		ERROR,
		FATAL,
		NUM_LOG_LEVELS,
	};
	
	class SourceFile
	{
	public:
		template<int N>
		inline SourceFile(const char (&arr)[N])
			:  data_(arr),
			   size_(N-1)
		{
			const char* slash = strrchr(data_,'/');
			if(slash)
			{
				data_ = slash + 1;
				size_ -= static_cast<int>(data_ - arr);
			}
		}

		explicit SourceFile(const char* filename)
			: data_(filename)
		{
			const char* slash = strrchr(data_,'/');
			if(slash)
			{
				data_ = slash + 1;
			}
			size_ = static_cast<int>(strlen(data_));
		}
		const char* data_;
		int size_;
	};

	Logger(SourceFile file,int line);
	Logger(SourceFile file,int line,LogLevel level);
	Logger(SourceFile file,int line,LogLevel level,const char* func);
	Logger(SourceFile file,int linei,bool toAbort);
	~Logger();
	
	LogStream& stream() {return impl_.stream_;}
	
	static LogLevel logLevel();
	static void setLogLevel(LogLevel level);
	
	typedef void (*OutputFunc)(const char* msg,int len);
	typedef void (*FlushFunc)();
	static void setOutput(OutputFunc);
	static void setFlush(FlushFunc);
private:
	class Impl
	{
	public:
		typedef Logger::LogLevel LogLevel;
		Impl(LogLevel level,int old_errno,const SourceFile& file,int line);
		void formatTime();
		void finish();

		Timestamp time_;
		LogStream stream_;
		LogLevel level_;
		int line_;
		SourceFile basename_;
	};
	
	Impl impl_;
};

在Logger内部,封装了两个类Source和Impl,还有enum LogLevel与若干构造函数、成员函数。下面详细说明一下。

1.1 SourceFile类

SourceFile类用于在输出信息的时候指明是在具体的出错文件。这个类不难,是一个简单的字符封装。它的私有成员有data_和size_。在Source的构造函数中,data_会被出错文件的路径初始化,然后经过strrchr处理,指向出错文件的basename(如/muduo/base/Thread.cc到Thread.cc)。size_就是basename的长度。

1.2 LogLevel

muduo使用枚举的方式指出了输出错误信息的形式。其中TRACE,DEBUG用于调试,INFO,WARN,ERROR,FATAL在程序运行过程中输出,警告的程度依次上升,出现FATAL时程序将强制退出。NUM_LOG_LEVELS是这个枚举结构体中错误形式的数目,为6。

1.3 Impl类

Impl类由Logger调用,输出出错信息。它有若干成员,下面依次分析。

1.3.1 formatTime函数

formatTime函数的作用是将出错的时间格式化问年-月-日-时-分-秒-毫秒的形式,并将格式化的时间字符串输入到stream_的缓冲区中。主要是利用了gmtime_r函数。

void Logger::Impl::formatTime()
{
	int64_t microSecondsSinceEpoch = time_.microSecondsSinceEpoch();
	time_t seconds = static_cast<time_t>(microSecondsSinceEpoch / 1000000);
	int microseconds = static_cast<int>(microSecondsSinceEpoch % 1000000);
	if(seconds != t_lastSecond)
	{
		t_lastSecond = seconds;
		struct tm tm_time;
		::gmtime_r(&seconds,&tm_time);
		int len = snprintf(t_time,sizeof t_time,"%4d%02d%02d %02d:%02d:%02d",
			tm_time.tm_year+1900,tm_time.tm_mon+1,tm_time.tm_mday,tm_time.tm_hour,
			tm_time.tm_min,tm_time.tm_sec);
		assert(len == 17);
		(void)len;
	}
	Fmt us(".%06dZ ",microseconds);
	assert(us.length() == 9);
	stream_ << T(t_time,17) << T(us.data(),9);
}

1.3.2 Impl构造函数

构造函数代码如下:构造函数中,我们用现在的时间初始化time_,line_是出错处的文件行号,basename就是出错文件的文件名。初始化接触后,执行formatTime函数,将当前线程号和LogLevel输入stream_的缓冲区。saveErrno我们一般传入errno(录系统的最后一次错误代码),若不等于0,将它错误原因输入到缓冲区。

Logger::Impl::Impl(LogLevel level,int savedErrno,const SourceFile& file,int line)
	: time_(Timestamp::now()),
	  stream_(),
	  level_(level),
	  line_(line),
	  basename_(file)
{
	formatTime();
	CurrentThread::tid();
	stream_ << T(CurrentThread::tidString(),6);
	stream_ << T(LogLevelName[level],6);
	if(savedErrno != 0)
	{
		stream_ << strerror_tl(savedErrno) << "(errno=" << savedErrno << ")";
	}
}

1.4 Logger构造函数与析构函数。

1.4.1 Logger构造函数

Logger构造函数大同小异,基本都是用参数来初始化impl_,如

Logger::Logger(SourceFile file,int line,LogLevel level,const char* func)
	:  impl_(level,0,file,line)
{
	impl_.stream_ << func << ' ';
}

1.4.2 Logger析构函数

Logger的析构函数代码如下:在一个Logger的生命周期结束之时,析构函数先调用finish函数将出错文件的行号与文件号加载到stream_中,然后使用g_output将缓冲区中的内容输出到标准输出或文件中,g_output默认使用fwrite将错误打印到标准输出。如果loglevel是FATAL,就要退出程序。

Logger::~Logger()
{
	impl_.finish();
	const LogStream::Buffer& buf(stream().buffer());
	g_output(buf.data(),buf.length());
	if(impl_.level_ == FATAL)
	{
		g_flush();
		abort();
	}
}

1.5 宏定义实现错误输出

在Logger类中定义了一系列宏来实现错误输出。我们使用Logger来输出错误信息时,只需调用这些宏即可。举例来说,当我们使用LOG_INFO << "info…"时,相当于用__FILE__ (文件的完整路径和文件名)与 LINE(当前行号的整数 )初始化了一个Logger类,出错时间、线程、文件、行号初始化号,接着将"infor"输入到缓冲区,在Logger生命周期结束时调用析构函数输出错误信息。

#define LOG_TRACE if(muduo::Logger::logLevel() <= muduo::Logger::TRACE) \
	muduo::Logger(__FILE__,__LINE__,muduo::Logger::TRACE,__func__).stream()
#define LOG_DEBUG if(muduo::Logger::logLevel() <= muduo::Logger::DEBUG) \
	muduo::Logger(__FILE__,__LINE__,muduo::Logger::DEBUG,__func__).stream()
#define LOG_INFO if(muduo::Logger::logLevel() <= muduo::Logger::INFO) \
	muduo::Logger(__FILE__,__LINE__).stream()
#define LOG_WARN muduo::Logger(__FILE__,__LINE__,muduo::Logger::WARN).stream()
#define LOG_ERROR muduo::Logger(__FILE__,__LINE__,muduo::Logger::ERROR).stream()
#define LOG_FATAL muduo::Logger(__FILE__,__LINE__,muduo::Logger::FATAL).stream()
#define LOG_SYSERR muduo::Logger(__FILE__,__LINE__,false).stream()
#define LOG_SYSFATAL muduo::Logger(__FILE__,__LINE__,true).stream()

猜你喜欢

转载自blog.csdn.net/MoonWisher_liang/article/details/107289227