ceph log源码分析
ceph中log的处理方法是,主线程生成并提交日志条目给log线程处理,典型用法如下:
ldout (cct, log_level)<< msg << dendl, 其中msg是要写入日志的内容.
以src/librbd/librbd.cc为例:
// 子系统名称,ceph的每个log模块是一个子系统
#define dout_subsys ceph_subsys_rbd
// 根据子系统重新定义dout_prefix,因为它最初定义在src/commom/dout.h中
#undef dout_prefix
#define dout_prefix *_dout << "librbd: "
// aio_read中log的用法, bl是读入数据的buffer
ldout(ictx->cct, 10) << "Image::aio_read() buf=" << (void *)bl.c_str() << "~"
<< (void *)(bl.c_str() + len - 1) << dendl;
宏ldout和dend定义在文件src/common/dout.h中,如下所示:
#define dout_prefix *_dout
// sub: 子系统名称, v: 日志级别
#define dout_impl(cct, sub, v) \
do { \
if (cct->_conf->subsys.should_gather(sub, v)) { \
if (0) { \
char __array[((v >= -1) && (v <= 200)) ? 0 : -1] __attribute__((unused)); \
} \
static size_t _log_exp_length=80; \
ceph::log::Entry *_dout_e = cct->_log->create_entry(v, sub, &_log_exp_length); \
ostream _dout_os(&_dout_e->m_streambuf); \
CephContext *_dout_cct = cct; \
std::ostream* _dout = &_dout_os;
#define ldout(cct, v) dout_impl(cct, dout_subsys, v) dout_prefix
#define dendl std::flush; \
_ASSERT_H->_log->submit_entry(_dout_e); \
} \
} while (0)
// 宏替换之前
ldout(ictx->cct, 10) << "Image::aio_read() buf=" << (void *)bl.c_str() << "~"
<< (void *)(bl.c_str() + len - 1) << dendl;
// 宏替换之后
do { \
if (cct->_conf->subsys.should_gather(ceph_subsys_rbd, v)) { \
if (0) { \
char __array[((v >= -1) && (v <= 200)) ? 0 : -1] __attribute__((unused)); \
} \
static size_t _log_exp_length=80; \
ceph::log::Entry *_dout_e = cct->_log->create_entry(v, ceph_subsys_rbd, &_log_exp_length); \
ostream _dout_os(&_dout_e->m_streambuf); \
CephContext *_dout_cct = cct; \
std::ostream* _dout = &_dout_os;
*_dout << "librbd: "<< "Image::aio_read() buf=" << (void *)bl.c_str() << "~"
<< (void *)(bl.c_str() + len - 1) << std::flush; \
_dout_cct->_log->submit_entry(_dout_e); \
} \
} while (0)
从以上代码可以看出,由宏ldout和dendl组成一条完整的do { … } while(0)代码块,并在do中进行日志的生成和提交.其中利用**create_entry(来生成一个日志条目,利用submit_entry()**来提交一个日志条目.这两个工作都是由主线程调用log线程进行的处理的.
日志定义
log子系统主要的实现在目录src/log中,里面有一个很重要的类Log,它继承自线程类,自带线程处理日志功能. 因为打印日志,会影响系统的性能,特别是c++的流,对性能影响更明显.ceph这里采取了一些优化:
- ceph 对每个子系统的日志都预先定义了日志级别,并且可动态修改.
- 每条log都带有日志级别,日志级别越低,优先级越高.低于预先定义的级别才会被打印.
- log 信息只需要提交到Log类的线程即可,由log线程接管后台打印日志的任务.对提交log的线程影响较小
Log类的实现比较简单,维护两个队列,m_new用于提交新日志,flush的时候获取m_new的entry用来刷新,m_recent用来存放最近的日志,比如用户通过admin socket发送dump log的命令时,就会将m_recent的日志dump到文件:
class Log : private Thread
{
Log **m_indirect_this;
SubsystemMap *m_subs; // 每个子系统的日志级别的map
pthread_mutex_t m_queue_mutex; // 这个锁专门用来提交日志
pthread_mutex_t m_flush_mutex; // 这个锁用来打印提交的日志
pthread_cond_t m_cond_loggers;
pthread_cond_t m_cond_flusher;