前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Tars-C++ 揭秘篇:日志类源码解析

Tars-C++ 揭秘篇:日志类源码解析

原创
作者头像
路小饭
修改2019-01-21 11:32:27
2.4K0
修改2019-01-21 11:32:27
举报

截止Tars-C++ 揭秘篇:链接管理,我们已经解析了RPC的大部分代码(promise部分以后单独再说)。可能有同学说没还看到epoll模型和协程部分的介绍啊,其实这部分在TarsRPC源码解读篇:使用C++重写Tars-RPC主逻辑框架中的第一节和第三节有所提及,所以剩下的的可能只有日志部分了

Tars的日志部分是自己实现的,翻看了代码,额,怎么说,日志部分的代码对初学者有一点点不友好。

  • 日志部分代码不够“单纯”,为了支持远程写日志,内嵌了一些“额外”的代码。这样不能直接把这部分代码拿出来在别的项目使用(为了方便一些同学想单独调试日志部分代码,笔者把那些“额外代码”去掉了,做了一个可以单独运行的模块,你可以在这里获取代码:Tars-log demo
  • 日志部分的类继承关系要复杂一些,且涉及到std::basic_streambuf< char >的自定义实现,一不小心掉坑里,可能就爬不出来了

本着“死贫道,不死道友”的精神,笔者对日志类进行了简要梳理,希望对想要了解Tars日志类的同学有些帮助

13.1 找准入口

如果想自己先梳理逻辑,可以按照下面的代码顺序进行

代码语言:txt
复制
// setLogInfo中,初始化了线程池,放入具体任务,循环等待输入内容
TarsRollLogger::getInstance()->setLogInfo(ServerConfig::Application, ServerConfig::ServerName, ServerConfig::LogPath, ServerConfig::LogSize, ServerConfig::LogNum, _communicator, ServerConfig::Log);

//设置日志等级
TarsRollLogger::getInstance()->logger()->setLogLevel("DEBUG");

//放入具体内容, setLogInfo中等待的线程写入文件
TarsRollLogger::getInstance()->logger()->debug() << "[TARS]" << "HelloWorld" << endl;

13.2 逻辑梳理

13.2Tars日志.jpg
13.2Tars日志.jpg

这张图画的的确有点乱。。。请担待

  • 日志的入口类为TarsRollLogger,里面有两个最主要的类TC_LoggerThreadGroup和TC_Logger< RollWriteT, TC_RollBySize >,分别位于图中左上角和最右边
  • TC_LoggerThreadGroup中有一个TC_ThreadPool的线程池,线程池里有一个ThreadWorker线程从_jobqueue中等待任务。
  • TC_LoggerThreadGroup::_LoggerThreadGroup::run中,一直在循环执行flush动作。flush在遍历logger_set,每次遍历会调用TC_LoggerRoll::flush(),将TC_ThreadQueue<pair<int, string> > _buffer中的内容写入文件。由此可以看出,弄清Tars日志工作机理,一是要追溯logger_set中的内容是从哪儿来的;二是_buffer中的内容是从哪儿来的,_buffer是logger_set的成员变量,所以先来看logger_set
  • logger_set的线索是蓝色框。我们已经对蓝色框进行了编号,1号位于TC_Logger< RollWriteT, TC_RollBySize >类中(右上角)。
  • 先看下TC_Logger的继承关系,TC_Logger继承:RollWrapperI,:RollWrapperI继承RollWrapperBase,1号中的setupThread具体实现就是在RollWrapperBase中,注意这里setupThread是通过调用2号蓝框中的_roll->setupThread实现的。而_roll是TC_RollBySize类(见绿色框),TC_RollBySize类继承TC_LoggerRoll,所以_roll->setupThread具体实现是在3号蓝色框中,即TC_LoggerRoll类中。
  • 在3号蓝色框中可以比较清晰的看到是怎样调用4号TC_LoggerThreadGroup::registerLogger方法,将TC_LoggerRoll放入logger_set中的
  • 继续看_buffer(TC_ThreadQueue),它的线索是黄色框。核心思想是重要的事儿是析构函数干的。
  • TC_Logger< RollWriteT, TC_RollBySize >在构造函数里偷偷干了两件事儿,一是将_roll(绿色框)作为变量传入LoggerBuffer中(1号黄色框),二是将LoggerBuffer变量传入了std::ostream _stream(2号黄色框)
  • 在进行“TarsRollLogger::getInstance()->logger()->debug()”的调用时,会调用3号黄色框LoggerStream stream(int level)方法,LoggerStream是一个临时类,调用完debug函数会析构,析构时候调用4号黄色框中stream->flush,而stream->flush将调用LoggerBuffer的sync(5号黄色框)将内容写入到_buffer(TC_ThreadQueue)中

贴一段验证代码,有助于理解5号黄色框__stream->flush调用LoggerBuffer的sync的逻辑

代码语言:txt
复制
#include <iostream>
#include <string>

using namespace std;

class LoggerBuffer : public std::basic_streambuf<char>
{
public:
    LoggerBuffer(int num){cout<<"LoggerBuffer init"<<endl;}

    ~LoggerBuffer(){cout<<"LoggerBuffer xigou"<<endl;}

	  virtual int sync(){cout<<"sync"<<endl;}

};

class TC_Logger
{
public:
    TC_Logger()
    :_buffer(1)
    , _stream(&_buffer)
    {}

	~TC_Logger(){cout<<"TC_Logger xigou"<<endl;}

    void stream(int level)
    {
        ostream *ost = NULL;
        ost = &_stream;
        //调用flush后会调用LoggerBuffer中的sync方法
		    ost->flush();
    }

    LoggerBuffer    _buffer;
    std::ostream    _stream;
};

int main()
{    
    TC_Logger logger;
    logger.stream(1);

	  cout<<"main fun end"<<endl;
	
    return 0;
}

g++ main.cpp

执行./a.out,会看到执行过程

下面这张stream关系图,对理解上面LoggerBuffer变量和ostream之间关系有些帮助

13.3stream.jpg
13.3stream.jpg

13.3 小结

  • 线程池部分逻辑很清晰,接收任务,循环等待,常规套路
  • LoggerBuffer继承了std::basic_streambuf< char >,重写了sync方法。ostream类型变量在调用flush时候,会调用重写的sync方法
  • Tars日志不仅支持按大小写入(TC_RollBySize),貌似也支持按时间写入(TC_RollByTime),没再仔细研究,感兴趣的小伙伴可自行探索

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 13.1 找准入口
  • 13.2 逻辑梳理
  • 13.3 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档