【Muduo源码剖析笔记】 基础组件之日志库
LogStream
FixedBuffer
一个固定大小的缓冲区,初始化在虚拟内存的初始化数据区。
包含char data__[SIZE]固定大小的缓冲区,一个char* cur_指针代表光标。一个空的函数指针cookie。
初始化函数:将光标指向data数组,设置cookie。
void append(const char* buf, size_t len):从buf指向的数组中,在当前光标位置复制len长度的数据。首先判断data_是否有这么多数据空位,有的话就进行memcpy,然后再将光标向前移动len。
length() : 返回目前数据长度,将当前光标减去 - data_ (指向数组的第一个数据)
avail() : 返回缓冲区还有多少空位,将end() 减去 cur_。
reset() : 将光标移至data_,清空缓冲区
toString():将data_的数据数据类型转换成string,这一步会分配空间应该。
toStringPiece:将data_的数据转换成String Piece,StringPiece是const char* prt_。https://www.cnblogs.com/my_life/articles/6143741.html所以这个类的目的是传入字符串的字面值,它内部的ptr_ 这块内存不归他所有。所以不能做任何改动。归根结底,是处于性能的考虑,用以实现高效的字符串传递,这里既可以用const char*,也可以用std::string类型作为参数,并且不涉及内存拷贝。
LogStream.h
包含了buffer_作为数据成员,封装了对buffer_的操作函数。对输入参数为不同类型的操作符<<作了定义和声明。
其中:
self& operator<<(float v):会把值v类型转换为double类型,再调用buffer_的append,返回*this。
self& operator<<(const char* str):如果str的指针非空,则会把str的内容append到buffer上。append接收一个指针,使用memcpy。如果str为空,则会输入null。
self& operator<<(const string& v):会将v转换为c_str
self& operator<<(const StringPiece& v):
self& operator<<(const Buffer& v):如果是另外一个Buffer,就会把v转换为StringPiece。再调用以上。
Fmt
包含一个32个字节的缓冲区和int类型表示长度。初始化的时候会把输入的T val值转换为字符串类型。会检查T是不是算术类型的值。
LogStream.cc
对不同类型的数据放入缓冲区的处理需要进行分类,
convert(char buf[], T value):Int到 String的转换,变成char类型,就能放入buffer_里了,
converHex(char buf[], uintptr_t value): 把int类型指针指向的数据转换成HEX型。
staticCheck(): 一般来说,数值类型的极值是一个与平台相关的特性。c++标准程序库通过template numeric_limits提供这些极值,取代传统C语言所采用的预处理常数。你仍然可以使用后者,其中整数常数定义于和<limits.h>,浮点常数定义于和<float.h>,新的极值概念有两个优点,一是提供了更好的类型安全性,二是程序员可借此写出一些template以核定这些极值。static_assert关键字,用来做编译期间的断言,因此叫做静态断言。
digits10 返回目标类型在十进制下可以表示的最大位数
formatInteger(T v): 如果buffer_的缓冲区空闲空间大于等于kMaxNumericSize(48),把buffer_的当前的光标开始,把v的值以char的形式写入buffer_中,然后对buffer的长度加上写进入的长度。
前面已经通过staticCheck保证最长的数据类型用十进制下表示的最大位数小于kMaxNumericSize,因此只要缓冲区空间空间大于kMaxNumericSize,就可以保存数据。在缓冲区内的数据都是用char类型保存的,普通的整型数字都是转换成十进制保存在buffer_里面。
operator<<(各种数字类型数据) : 会将各种类型的数据调用formatInteger,写入buffer_中。short类型和unsigned short类型会把数据类型转换成int和unsigned int类型再调用formatInteger。
LogStream::operator<<(const void* p): 把p指针先转化成int*,然后把p指向的数据放进buffer_里面,首先会以0x开头,然后再放p指向的数据。以十六进制进行存放。
LogStream::operator<<(double v): double类型的数据会用snprintf转换成字符串,再放入buffer_
Logger
包含SourceFile、Impl两个类。
用enum设置了LogLevel。
4个初始化函数
SourceFile
包含一个const char* data_保存了源文件的名字。size_表示存放的源文件的名字长度。
准备了两个初始化函数,一个接收const char[]数组,需要一个N作为模板参数。利用strrchr找到’/'后的第一个char,但是把后续的文件名字赋值给data_。利用指针的差值作为长度。
第二个初始化函数接收一个const char*指针。
Impl
资源类,包含了一个时间戳,一个line_,一个Logstream,一个源文件,一个Log级别。
AsyncLoggin
包含一个AsyncLogging类,包含一个flushInteval_数据成员,running_数据成员,basename_数据成员,rollSize_数据成员,thread_数据成员,latch_数据成员,mutex_数据成员,cond_数据成员,两个缓冲区的数据成员currentBuffer_和nextBuffer_和一个ufferVector buffers_。
AsyncLogging::append(const char* logline, int len):先创建临界区,保护缓冲区。如果目前的缓冲区空间大于数据长度,那么就把数据放入currentBuffer中。否则把currentBuffer_右值化后放入buffers_中。如果nextBuffer_有的话,那么就把nextBuffer_转换成currentBuffer_。最后再把数据放入currentBuffer_,并且发送条件变量通知。
AsyncLogging::threadFunc():是日志线程运行的函数,初始化两个Buffer指针,newBuffer1和newBuffer2.(用new出来的,放在heap上)。局部变量再多一个BufferVector buffersToWrite。初始化完成。
开启临界区,把currentBuffer_的内容放入buffers_中,然后把newBuffer1移动给currentBuffer_。
LogFile
日志线程里面会有一个LogFile,日志线程往output调用append写入log信息的Buffer。
LogFile类里面有一个AppendFile对象的指针,由这个对象的指针管理写入的东西。
包含一个 string basename_数据成员,有off_t类型的rollsize_数据成员,有const int类型flushInterval_,const int类型的checkEveryN_,有int类型的count_。有一个mutex_,3个time_t类型数据,一个由unique_ptr指向自己的指针 file_。
初始化函数中,如果是线程安全的话,就会new 一个MutexLock给mutex_。不过AsyncLogging那边是false,因此不会使用mutex_,同时调用rollfile()函数。
重要函数:
LogFile::rollFile():会获取FileName的名字,然后将file_成员new出一个新的AppendFile对象,可以往这个对象里面写东西。
append(const char* logline, int len):有无锁和有锁的版本,因为不知道会以锁的形式调用这个函数还是无锁的情况下调用这个函数,最后都是调用append_unlocked。
LogFile::append_unlocked(const char* logline, int len):会调用