tars开源框架地址:https://github.com/Tencent/Tars
系列文章:
鹅厂开源框架tars之日志服务
鹅厂开源框架tars之运营监控服务
鹅厂开源框架tars之基础组件
鹅厂开源框架tars之网络层实现
简介:Tars是腾讯从2008年到今天一直在使用的后台逻辑层的统一应用框架TAF(Total Application Framework),目前支持C++,Java,PHP,Nodejs语言。该框架为用户提供了涉及到开发、运维、以及测试的一整套解决方案,帮助一个产品或者服务快速开发、部署、测试、上线。 它集可扩展协议编解码、高性能RPC通信框架、名字路由与发现、发布监控、日志统计、配置管理等于一体,通过它可以快速用微服务的方式构建自己的稳定可靠的分布式应用,并实现完整有效的服务治理。目前该框架在腾讯内部,各大核心业务都在使用,颇受欢迎,基于该框架部署运行的服务节点规模达到上万个。
之前把tars的日志放在另外一篇专门讲tars组件的文章里面了,发现写下去由于tars通用组件比较多,文章太长,所以特地把日志功能独立出来。tars的日志服务器分本地的流水日志,还有远程的日志:本地日志一般用来记录一些程序debug的滚动日志,远程的日志用来汇总业务的统计日志等统一发送到远程的日志服务器,远程的日志服务器配置在config.conf文件的log选项里面:例如
log=LogServer.taflog4xxxsh.LogObj 代表远程日志统一写到这个远程日志服务里面,日志服务器一般用一到n台大磁盘的机器来部署logserver(大业务量的机器,一台日志服务器可能支撑不住)
一、下面先看下本地滚动日志的实现和使用方法:
业务使用本地滚动日志的方式:例如
LOG_ROLL_DEBUG("Uin:" << user->getUin() << ",iExtraScore:" << iExtraScore << ",iMaxExtraScore:" << iMaxExtraScore);
用于记录某个uin的额外分数和最大分数。
LOG_ROLL_DEBUG的宏定义:
#define LOG_DEBUG LOG->debug()<< "[" << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "]|"
下面看LOG宏的定义
#define LOG (TafRollLogger::getInstance()->logger())
TafRollLogger单体类的定义如下:
class TafRollLogger : public TC_Singleton
TC_Singleton的细节放在另外一篇文章《鹅厂开源框架tars之基础组件》,这里CreateUsingNew代表在堆中用new创建TafRollLogger ,并且定义对象的生命周期为不死(就是如果对象已经析构,再调用会自动创建一个)
TafRollLogger 类定义于Taf_logger.h 文件,成员变量RollLogger _logger为TafRollLogger 类的成员变量,通过成员函数logger() 返回成员变量_logger,定义为:typedef TC_Logger
如上图可以看到(TafRollLogger::getInstance()->logger()) 用于返回TafRollLogger类的成员变量TC_Logger,TC_Logger的定义
typedef TC_Logger
RollWriteT重载了:operator():
void RollWriteT::operator()(ostream &of, const deque
{
......
of << it->second; //这里把ds的string内容流化到of
......
of.flush(); //缓冲落地
}
而第二个模板参数TC_RollBySize类继承了TC_LoggerRoll类,TC_LoggerRoll类定义于Tc_logger.h文件,官方注释为具体写日志基类,TC_LoggerRoll类的成员函数write()会调用写日志函数,而write函数的调用时机是在LoggerBuffer的析构函数里面调用sync()函数,sync()调用TC_LoggerRoll的write函数,LoggerBuffer为TC_Logger类的成员函数,因此在析构的时候会调用write函数,具体怎么write的看下:TC_RollBySize类的roll函数:void roll(const deque
roll函数的实现,第一步就调用了:TC_LockT
小结下:有了以上的分析,看下项目实际写日志文件的流程是怎么样的:
1.调用LOG_ROLL_DEBUG("Uin:" << user->getUin() << ",iExtraScore:" << iExtraScore << ",iMaxExtraScore:" << iMaxExtraScore);
LOG_ROLL_DEBUG为宏定义,宏定义如下:
#define LOG_ROLL_DEBUG(context) do { if (IS_LOG_DEBUG) { LOG->debug()<< "[" << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "]|" << context << endl; } } while (0)
LOG宏的定义:(TafRollLogger::getInstance()->logger()),通过上面的分析这里logger()函数会返回成员变量_logger,_logger的定义如下:typedef TC_Logger
2.由调用代码可以看到,构造完TafRollLogger类,下一步调用TC_Logger的debug函数,这里会调用stream函数传入level参数(DEBUG_LOG = 4,服务器的日志级别读配置文件,如果没配默认为debug级别),函数会生成LoggerStream类返回,注意这里会调用head函数写入日志的日期时间等日志头信息,LoggerStream的构造传入的是TC_Logger的成员变量_stream,而_stream在TC_Logger构造的时候是用的成员变量_buffer初始化的,所以其实这里最终操作的是成员变量_buffer。同时返回的LoggerStream类重载了<<操作符(std::ostream *_stream; LoggerStream& operator << (const P &t) { if (_stream) *_stream << t;return *this;})可见会被日志信息加入到_stream成员变量操作流。由于这里LoggerStream类的_stream构造的时候是用TC_Logger的成员函数std::ostream _stream;赋值的,所以实际操作的都是TC_Logger类的_buffer成员函数,而~LoggerBuffer在析构的时候会调用sync()同步函数,调用_roll->write(make_pair(0, string(pbase(), len)));从第一点的TC_Logger的构造分析,知道本地滚动日志默认是传入的TC_RollBySize类构造的,所以这里会调用TC_RollBySize类的成员函数roll():roll函数会根据传入的buffer队列写入到系统框架指定好的log文件,从而完成滚动日志的写入。这里有一个需要注意的地方是:LOG->debug()直接调用LoggerStream类的构造函数返回的是LoggerStream的临时对象,临时对象在函数调用完就会触发析构,所以这个时候就开始写日志,至此完成了滚动日志使用者一次日志的调用到写入日志文件的过程(由于这里写日志的过程加了线程锁,所以滚动日志是多线程安全的)。下面分析下远程日志的实现过程
二、远程日志TafTimeLogger
同理,先看下远程日志调用者的使用方法,如下:
LOG_STAT("stat_add_exp") << user->getUin() << "|" << getLevel(user) << "|" << iExp << endl;
这里输出玩家账号下面:等级和经验的变化日志,LOG_STAT宏的定义如下:
#define LOG_STAT(type) FDLOG(type)
#define FDLOG(x) (TafTimeLogger::getInstance()->logger(x)->any())
注意:这里生成的是TafTimeLogger类,而不是第一点写滚动日志生成的TafRollLogger类
TafTimeLogger官方注释为TafTimeLogger类,可见TafTimeLogger类是专门用于写远程日志的。tars框架在Application的initializeServer函数,会调用如下函数设置远程日志参数:
TafTimeLogger::getInstance()->setLogInfo(_communicator, ServerConfig::Log, ServerConfig::Application, ServerConfig::ServerName, ServerConfig::LogPath,setDivision()); 这里设置了app名字,通讯器,服务器名字,日志路径,set等信息,注意,如果传入的ServerConfig::Log obj不为空,则会调用 _logPrx = _comm->stringToProxy
2.1.宏定义里面第一步调用TafTimeLogger::getInstance()->logger(x)这里x为传入的"stat_add_exp"参数,logger函数调用
TimeLogger *p = new TimeLogger();生成TimeLogger 类,并加入到成员变量map
2.2 logger(x)->any()调用的函数原型LoggerStream any() { return stream(-1);} 跟第一点写滚动日志的区别这里stream传入了-1,也就是无论日志服务器设置为任何级别都会写日志,同理这里LoggerStream重载了操作符operator << 所以后面的<< user->getUin() 等日志信息会写入流:_stream。跟滚动日志同理,这里构造了LoggerStream 临时类,析构的时候同理调用
TC_Logger类传入的模板参数TC_RollByTime类的roll函数,这里时间日志支持按天,按小时、按分钟格式独立一个文件。
三,关于LoggerBuffer类
定义如下: class LoggerBuffer : public std::basic_streambuf
LoggerBuffer定义于TC_Logger类的LoggerBuffer _buffer;在TC_Logger类构造的时候传入到成员函数_stream,并且在stream(int level)生成LoggerStream临时类的时候会进行如下的操作:
ost = &_stream; //使用自定义的basic_streambuf
_stream.clear();
_stream << c;
}
return LoggerStream(ost, &_estream, _mutex); //使用自定义的basic_streambuf
关于自定义的basic_streambuf,
1、pbase()是缓冲区的开始
2、pptr()是当前写入的位置
3、epptr()是缓冲区结尾
4.重写overflow()函数,overflow()的作用是当缓冲区满时将数据写入(这里定义最大的buffer大小(10M),也就是大于10m会调用成员LoggerBuffer类的函数sync开始写数据)