ceph中原有日志功能介绍
当前ceph中的日志只能做到某一子系统(subsystem)的某一级别日志,比如给ceph_subsys_osd子系统设置日志级别为5,则表示任何级别小于等于5的osd子系统日志都可以输出到日志文件。关于子系统日志级别设置有两种方法
(1)在配置文件中指定
比如
debug_osd 5
就是将osd子系统的日志输出级别设置为5
(2)利用admin_socket动态设置
ceph实现了admin_socket(即unix socket)来用于用户和ceph的组件进行通信。我们可以利用admin_socket来动态设置日志输出级别,如下:
ceph daemon /var/run/ceph/ceph-osd.0.asok congfig set debug_osd 5
在调试问题时一般会用到第二种方法进行运行时更改日志输出级别,但是这个方法有一个问题就是会输出大量不相关的日志信息,这会增加问题排查的难度,因此本人修改ceph日志模块,增加了可以具体到某一函数的日志输出功能,并可以利用admin_socket动态修改.
函数级别日志输出
增加函数级别日志的动态设置、查看、删除等功能,具体实现如下
(1)定义相关类与方法
在src/log/SubsystemMap.h中定义相关的类和方法
class ZyLog
{
private:
std::map<unsigned, std::set<std::string>> zy_log_funs;
public:
void zy_set_fun_log(unsigned subsys, std::string fun_name)
{
zy_log_funs[subsys].insert(fun_name);
}
void zy_unset_fun_log(unsigned subsys, std::string fun_name)
{
zy_log_funs[subsys].erase(fun_name);
if(zy_log_funs[subsys].empty())
{
zy_log_funs.erase(subsys);
}
}
bool zy_should_gather(unsigned subsys, std::string fun_name)
{
return (zy_log_funs[subsys].find(fun_name) == zy_log_funs[subsys].end());
}
int zy_subsys_string_to_unsigned(std::string subsys_str)
{
constexpr auto t = ceph_subsys_get_as_array();
for(unsigned i = 0; i < t.size(); i++)
{
if(std::string(t[i].name) == subsys_str)
return i;
}
return -1;
}
void get_all_subsys(Formatter *f)
{
auto t = ceph_subsys_get_as_array();
for(unsigned i = 0; i < t.size(); i++)
{
f->dump_stream(t[i].name);
}
}
void get_all_setted_funs(Formatter *f)
{
for(auto subsys_it = zy_log_funs.begin(); subsys_it != zy_log_funs.end(); subsys_it++)
{
if(subsys_it->first >= ceph_subsys_get_num())
continue;
const auto subsys_name = ceph_subsys_get_as_array()[subsys_it->first].name;
for(auto fun_it = subsys_it->second.begin(); fun_it != subsys_it->second.end(); fun_it++)
f->dump_string(subsys_name, *fun_it);
}
}
(2)注册相关命令
在src/common/ceph_context.cc的CephContext类的构造函数中,增加如下几行
_admin_socket->register_command("zylog set", "zylog set name=subsys,type=CephString name=fun,type=CephString", _admin_hook, "");
_admin_socket->register_command("zylog unset", "zylog unset name=subsys,type=CephString name=fun,type=CephString", _admin_hook, "");
_admin_socket->register_command("zylog showsubsys", "zylog showsubsys", _admin_hook, "");
_admin_socket->register_command("zylog showsets", "zylog showsets", _admin_hook, "");
这里分别注册了set unset showsubsys showsets四个命令,其中showsubsys是查看ceph中有哪些子系统可以设置
set命令的用法就是ceph daemon /var/run/ceph/ceph-osd.0.asok zylog set osd heartbeat ,表示设置osd子系统中的heartbeat函数中的日志为输出。
(3)处理相关命令
在src/common/ceph_context.cc的do_command函数中增加如下代码
else if (command == "zylog set") {
std::string zy_subsys;
std::string zy_fun;
if (!(cmd_getval(this, cmdmap, "subsys", zy_subsys)) ||
!(cmd_getval(this, cmdmap, "fun", zy_fun))) {
f->dump_string("error", "syntax error: 'config set <subsys> <fun>'");
} else {
int subsys_id = _conf->funlog.zy_subsys_string_to_unsigned(zy_subsys);
if(-1 == subsys_id)
f->dump_stream("error") << zy_subsys << " is not a subsys";
else
{
_conf->funlog.zy_set_fun_log((unsigned)subsys_id, zy_fun);
f->dump_stream("success") << " set " << zy_subsys << "(" << subsys_id << ") " << zy_fun;
}
}
}
else if (command == "zylog unset") {
std::string zy_subsys;
std::string zy_fun;
if (!(cmd_getval(this, cmdmap, "subsys", zy_subsys)) ||
!(cmd_getval(this, cmdmap, "fun", zy_fun))) {
f->dump_string("error", "syntax error: 'config set <subsys> <fun>'");
} else {
int subsys_id = _conf->funlog.zy_subsys_string_to_unsigned(zy_subsys);
if(-1 == subsys_id)
f->dump_stream("error ") << zy_subsys << "is not a subsys";
else
{
_conf->funlog.zy_unset_fun_log((unsigned)subsys_id, zy_fun);
f->dump_stream("success ") << "unset" << zy_subsys << "(" << subsys_id << ") " << zy_fun;
}
}
}
else if (command == "zylog showsubsys") {
_conf->funlog.get_all_subsys(f);
}
else if (command == "zylog showsets") {
_conf->funlog.get_all_setted_funs(f);
}
(4)修改dout.h
修改后的内容如下
#define dout_impl(cct, sub, v) \
do { \
unsigned funsub = sub; \
const string funname = __func__; \
bool funlog_out = false; \
const bool should_gather = [&](const auto cctX) { \
if constexpr (ceph::dout::is_dynamic<decltype(sub)>::value || \
ceph::dout::is_dynamic<decltype(v)>::value) { \
return cctX->_conf->subsys.should_gather(sub, v); \
} else { \
/* The parentheses are **essential** because commas in angle \
* brackets are NOT ignored on macro expansion! A language's \
* limitation, sorry. */ \
return (cctX->_conf->subsys.template should_gather<sub, v>()); \
} \
}(cct); \
\
if (!should_gather){ \
funlog_out = !(cct->_conf->funlog.zy_should_gather(funsub, funname)); \
} \ //如果日志在原有判断中不能输出,则继续判断函数级别日志是否允许输出
if (should_gather || funlog_out) { \
static size_t _log_exp_length = 80; \
ceph::logging::Entry *_dout_e = NULL; \
if(!funlog_out) { \
_dout_e = \
cct->_log->create_entry(v, sub, &_log_exp_length); \
} \
else { \
_dout_e = \
cct->_log->create_entry(-1, sub, &_log_exp_length); \ //如果函数级别日志允许输出,则需要将改条日志的level改为-1,这样是为了防止在submit_entry日志时再次被过滤掉。
} \
static_assert(std::is_convertible<decltype(&*cct), \
CephContext* >::value, \
"provided cct must be compatible with CephContext*"); \
auto _dout_cct = cct; \
std::ostream* _dout = &_dout_e->get_ostream();
最后附上代码修改记录
https://github.com/jasonlinuxzhang/ceph/commit/c6b5a14c8ea0bfd485fd30bc0afdf74f1f2f137e
需要改进的地方
(1)在设置函数级别日志输出时,我没有判断该函数是否存在于子系统内,目前我还没有找到方法获得某个子系统内或者某个文件内的函数表。
(2)可以设置命令格式为file:function而不是subsys:function,这样就可以具体到某一个文件中的某个函数,而不是某一个某一个子系统,目前也没有找到修改方法。