Ceph 服务器日志系统通过宏定义和线程机制实现高效日志记录。首先,通过枚举和数组定义日志子系统(如osd、mon),并利用 dout 、 dendl 等宏展开为条件判断和日志组装代码。服务启动时,global_init 初始化上下文并创建独立的 log 线程作为消费者。日志输出时,生产者通过宏替换生成实际代码,判断日志级别后,将日志条目提交到队列,若队列满则阻塞。 log 线程从队列取出日志,根据配置将日志输出到文件、 stderr 、 syslog 、 graylog 或 journald 等多个目标,支持灵活的日志级别和输出控制。整个流程实现了异步、可配置的多目标日志记录。

注意: 以下的分析基于 Ceph V20.2.0

一、服务器侧日志

1.1、日志环境准备

1.1.1、日志子系统

按照 官方文档 中提到的日志子系统的配置。

注意:

  • 在提交日志的时候,只要日志的级别在 max(日志级别, 内存级别) 的范围内,都会提交到日志队列中;
  • 所有提交到日志队列中的日志都会被记录到内存中的 m_recent 环中 (环的大小受限于 log_max_recent 配置), 以便在需要时( 如崩溃后调用 dump_recent() )能够查看最近的日志历史;
  • 但是只有在 日志级别 范围内的日志才会输出到日志中;
子系统日志级别内存级别
default05
lockdep01
context01
crush11
mds15

设置对应日志级别的函数定义:

  • 从下面可以看出,第一个值对应的是 log_level , 第二个值对应的是 gather_level ;
  • 实际设置的 m_gather_levels[subsys] 的值为 log_level 和 gather_level 的较大值;
void ConfigValues::set_logging(int which, const char *val) {
int log, gather;
int r = sscanf(val, "%d/%d", &log, &gather);
if (r >= 1) {
if (r < 2) {
gather = log;
}
subsys.set_log_level(which, log);
subsys.set_gather_level(which, gather);
#if WITH_CRIMSON
crimson::get_logger(which).set_level(crimson::to_log_level(log));
#endif
}
}

void set_log_level(unsigned subsys, uint8_t log) {
ceph_assert(subsys < m_subsys.size());
m_subsys[subsys].log_level = log;
m_gather_levels[subsys] = std::max(log, m_subsys[subsys].gather_level);
}

void set_gather_level(unsigned subsys, uint8_t gather) {
ceph_assert(subsys < m_subsys.size());
m_subsys[subsys].gather_level = gather;
m_gather_levels[subsys] = std::max(m_subsys[subsys].log_level, gather);
}

日志子系统的枚举定义:

// 枚举定义
enum ceph_subsys_id_t
{
ceph_subsys_, // default
#define SUBSYS(name, log, gather) ceph_subsys_##name,
#define DEFAULT_SUBSYS(log, gather)
#include "common/subsys.h"
#undef SUBSYS
#undef DEFAULT_SUBSYS
ceph_subsys_max
};


// 展开宏之后的枚举定义示例
enum ceph_subsys_id_t
{
ceph_subsys_, // default (0)
ceph_subsys_osd, // 1
ceph_subsys_mon, // 2
ceph_subsys_client, // 3
ceph_subsys_max // 4
};

日志子系统的数组定义:

// 函数定义
constexpr static std::array<ceph_subsys_item_t, ceph_subsys_get_num()> ceph_subsys_get_as_array()
{
#define SUBSYS(name, log, gather) ceph_subsys_item_t{#name, log, gather},
#define DEFAULT_SUBSYS(log, gather) ceph_subsys_item_t{"none", log, gather},

return {
#include "common/subsys.h"
};
#undef SUBSYS
#undef DEFAULT_SUBSYS
}

// 展开宏之后的函数定义示例
constexpr static std::array<ceph_subsys_item_t, 4> ceph_subsys_get_as_array()
{
return {
ceph_subsys_item_t{"none", 0, 0}, // DEFAULT_SUBSYS(0, 0)
ceph_subsys_item_t{"osd", 1, 5}, // SUBSYS(osd, 1, 5)
ceph_subsys_item_t{"mon", 1, 5}, // SUBSYS(mon, 1, 5)
ceph_subsys_item_t{"client", 0, 5}, // SUBSYS(client, 0, 5)
};
}

日志子系统的最长名称定义: (目前该函数没有被实际使用。)

// 函数定义
constexpr static std::size_t ceph_subsys_max_name_length()
{
return std::max({
#define SUBSYS(name, log, gather) strlen_ct(#name),
#define DEFAULT_SUBSYS(log, gather) strlen_ct("none"),
#include "common/subsys.h"
#undef SUBSYS
#undef DEFAULT_SUBSYS
});
}

// 展开宏之后的函数定义示例
constexpr static std::size_t ceph_subsys_max_name_length()
{
// 展开为:
return std::max({
strlen_ct("none"), // DEFAULT_SUBSYS(0, 0) → "none" (4个字符)
strlen_ct("osd"), // SUBSYS(osd, 1, 5) → "osd" (3个字符)
strlen_ct("mon"), // SUBSYS(mon, 1, 5) → "auth" (4个字符)
strlen_ct("client"), // SUBSYS(client, 0, 5) → "client" (6个字符)
});
}

1.1.2、日志输出宏定义

日志输出示例:

dout(1) << " ignoring boot message without a port" << dendl;

相关宏:

  • dout(v): 输出日志调用的日志开头的宏;
  • dout_context:
  • dendl: 输出日志调用的日志结尾的宏;
  • dendl_impl: 由 dendl 宏定义中使用的宏,用于拼接输出日志的代码逻辑块;
  • ldout(cct, v): 由 dout(v) 宏定义中使用的宏;
  • dout_impl(cct, sub, v): 由 ldout(cct, v) 宏定义中使用的宏;
  • dout_subsys: 由 ldout(cct, v) 宏定义中使用的宏;
  • dout_prefix: 由 ldout(cct, v) 宏定义中使用的宏;

相关宏定义代码:

// src/common/debug.h 文件中定义 dout 宏
#define dout(v) ldout((dout_context), (v))

// 众多文件中定义 dout_context 宏
// 以下是一个示例,其中 g_ceph_context 是 ceph 全局上下文对象
#define dout_context g_ceph_context

// src/common/dout.h 文件中定义 ldout 宏
#define ldout(cct, v) dout_impl(cct, dout_subsys, v) dout_prefix

// src/common/dout.h 文件中定义 dendl 宏
#define dendl dendl_impl

// 众多文件中定义 dout_subsys 宏
// 以下是一个示例
#define dout_subsys ceph_subsys_auth

// 众多文件中定义 dout_prefix 宏
// 以下是一个示例
#undef dout_prefix
#define dout_prefix *_dout << "auth: "

// src/common/dout.h 文件中定义的 dout_impl 和 dendl_impl 宏
#ifdef WITH_CRIMSON
#define dout_impl(cct, sub, v) \
do { \
if (crimson::common::local_conf()->subsys.should_gather(sub, v)) { \
seastar::logger &_logger = crimson::get_logger(sub); \
const auto _lv = v; \
std::ostringstream _out; \
std::ostream *_dout = &_out;
#define dendl_impl \
""; \
_logger.log(crimson::to_log_level(_lv), "{}", _out.str().c_str()); \
} \
} \
while (0)
#else
#define dout_impl(cct, sub, v) \
do { \
const bool should_gather = [&](const auto cctX, auto sub_, auto v_) { \
/* The check is performed on `sub_` and `v_` to leverage the C++'s \
* guarantee on _discarding_ one of blocks of `if constexpr`, which \
* includes also the checks for ill-formed code (`should_gather<>` \
* must not be feed with non-const expresions), BUT ONLY within \
* a template (thus the generic lambda) and under the restriction \
* it's dependant on a parameter of this template). \
* GCC prior to v14 was not enforcing these restrictions. */ \
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 { \
constexpr auto sub_helper = static_cast<decltype(sub_)>(sub); \
constexpr auto v_helper = static_cast<decltype(v_)>(v); \
/* 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_helper, v_helper>()); \
} \
}(cct, sub, v); \
\
if (should_gather) { \
ceph::logging::MutableEntry _dout_e(v, sub); \
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();

// 展开 dout_prefix 宏内容,比如:
// *_dout << "auth: "

// 继续输出对应的日志内容,比如:
// << " ignoring boot message without a port" << dendl;

#define dendl_impl \
std::flush; \
_dout_cct->_log->submit_entry(std::move(_dout_e)); \
} \
} \
while (0)
#endif // WITH_CRIMSON

1.1.3、日志线程初始化

每个服务组件启动的时候基本都会调用 global_init 函数进行一些初始化的流程,在这个流程中会初始化每个服务输出日志所需要的 context ,并创建处理日志的 log 线程。

创建 log 线程流程示意图:

graph LR
    A[global_init] --> B[global_pre_init]

    B --> C[common_preinit <br/>初始化context]
    C --> C1[CephContext::CephContext <br/>初始化 log 对象]

    B --> D[global_init_set_globals<br/>设置 g_ceph_context]

    B --> E[Log::start<br/>创建名为 log 的线程]

    E --> F[Thread::create]
    F --> G[Thread::try_create]
    G --> H[Thread::_entry_func]
    H --> I[Thread::entry_wrapper]
    I --> J[Log::entry]

    style E fill:#e8f5e8,stroke:#333,stroke-width:1px
    style J fill:#e8f5e8,stroke:#333,stroke-width:1px

创建 log 线程流程:

+ global_init -> global_pre_init
+ global_pre_init -> common_preinit -初始化context-> CephContext::CephContext(初始化 log 对象)
+ global_pre_init -> global_init_set_globals(设置 g_ceph_context)
+ global_pre_init -> Log::start -创建名为 log 的线程-> Thread::create -> Thread::try_create -> Thread::_entry_func -> Thread::entry_wrapper -> Log::entry

1.2、日志输出流程

当执行日志输出的,我们需要经过一些宏替换,并实现最终的输出日志的代码。

  • 原始的输出代码示例: dout(1) << "Updating MDS map to version " << epoch << " from " << m->get_source() << dendl;
  • 实际的输出日志示例: 2025-11-21T03:07:59.122+0800 7f8365678700 1 mds.node01 Updating MDS map to version 158945 from mon.0

1.2.1、日志生产者

代码中的这种输出日志的代码 dout(1) << "Updating MDS map to version " << epoch << " from " << m->get_source() << dendl; ,实际在运行的时候会被替换为如下的示例代码:

// 展开 dout 及相关的宏
do {
const bool should_gather = [&](const auto cctX, auto sub_, auto v_) {
if constexpr (ceph::dout::is_dynamic<decltype(sub_)>::value ||
ceph::dout::is_dynamic<decltype(v_)>::value) {
return cctX->_conf->subsys.should_gather(dout_subsys, 1);
} else {
constexpr auto sub_helper = static_cast<decltype(sub_)>(dout_subsys);
constexpr auto v_helper = static_cast<decltype(v_)>(1);
return (cctX->_conf->subsys
.template should_gather<sub_helper, v_helper>());
}
}(dout_context, dout_subsys, 1);

if (should_gather) {
ceph::logging::MutableEntry _dout_e(1, dout_subsys);
static_assert(
std::is_convertible<decltype(&*dout_context), CephContext *>::value,
"provided cct must be compatible with CephContext*");
auto _dout_cct = dout_context;
std::ostream *_dout = &_dout_e.get_ostream();

// 展开 dout_prefix 宏
*_dout << "mds." << name << ' '

// 用户输出的日志内容以及 dendl 宏
// << "Updating MDS map to version " << epoch << " from " << m->get_source() << dendl;
<< " Updating MDS map to version 158945 from mon.0" << std::flush;
_dout_cct->_log->submit_entry(std::move(_dout_e));
}
} while (0);

注意: 日志在提交前会调用 should_gather 函数来判断当前的日志级别是否符合当前子系统的约束,否则不会将对应的日志提交到日志队列中。

bool should_gather(const unsigned sub, int level) const {
ceph_assert(sub < m_subsys.size());
return level <= static_cast<int>(m_gather_levels[sub]);
}

之后就走到了 Log::submit_entry 函数的执行逻辑中,注意: 如果日志队列中的元素超过 m_max_new (对应 log_max_new 参数, 默认为 1000) ,则线程会阻塞等待,直到队列有空闲位置。当将需要记录的日志提交到 m_new 中之后,就会通过条件变量来通知 log 线程处理日志。

void Log::submit_entry(Entry &&e)
{
std::unique_lock lock(m_queue_mutex);
m_queue_mutex_holder = pthread_self();

if (unlikely(m_inject_segv))
*(volatile int *)(0) = 0xdead;

// wait for flush to catch up
while (is_started() &&
m_new.size() > m_max_new)
{
if (m_stop)
break; // force addition
m_cond_loggers.wait(lock);
}

m_new.emplace_back(std::move(e));
m_cond_flusher.notify_all();
m_queue_mutex_holder = 0;
}

1.2.2、日志消费者

按照之前的描述,每个服务都有一个处理日志的名为 log 的线程,对应的线程入口函数为 Log::entry ,其内部关键的写日志的函数为 Log::_flush 。分析其具体实现,我们发现 ceph 支持将日志存储到五个地方,分别如下:

输出目标正常日志阈值崩溃日志阈值是否默认启用启用方法
file子系统级别子系统级别是(如果设置文件)set_log_file()
stderrm_stderr_logm_stderr_crashset_stderr_level()
syslogm_syslog_logm_syslog_crashset_syslog_level()
graylogm_graylog_logm_graylog_crashstart_graylog()
journaldm_journald_logm_journald_crashstart_journald_logger()

file 相关配置:

  • log_to_file: 是否输出日志到文件;
  • log_file: 指定输出日志文件的路径,默认路径为 /var/log/ceph/$cluster-$name.log

stderr 相关配置:

  • log_to_stderr:
    • 是否将日志日志输出到标准错误 stderr ,默认为 true ;
    • 如果为 true 表示将所有日志 (最高级别 99) 输出到 stderr ;
  • err_to_stderr:
    • 是否将错误日志输出到标准错误 stderr ,默认为 false ;
    • 如果 log_to_stderr 为 false 且 err_to_stderr 为 true ,表示只将错误日志输出到 stderr ;
    • 如果 log_to_stderr 为 false 且 err_to_stderr 为 false , 表示不向 stderr 输出任何日志;
  • log_stderr_prefix: 为输出到标准错误 stderr 的每条日志消息添加一个固定的前缀字符串

syslog 相关配置:

  • log_to_syslog:
    • 是否将日志写入系统日志中,默认为 false ;
    • 如果为 true 表示将所有日志 (最高级别 99) 输出到 syslog ;
  • err_to_syslog:
    • 是否将异常日志写入系统日志中,默认为 false ;
    • 如果 log_to_syslog 为 false 且 err_to_syslog 为 true ,表示只将错误日志输出到 syslog ;
    • 如果 log_to_syslog 为 false 且 err_to_syslog 为 false , 表示不向 syslog 输出任何日志;

graylog 相关配置:

  • log_to_graylog:
    • 是否将日志写入远程的 graylog 服务中,默认为 false ;
    • 如果为 true 表示将所有日志 (最高级别 99) 输出到远程的 graylog 服务中;
  • err_to_graylog:
    • 是否将异常日志写入远程的 graylog 服务中,默认为 false ;
    • 如果 log_to_graylog 为 false 且 err_to_graylog 为 true ,表示只将错误日志输出到远程的 graylog 服务中;
    • 如果 log_to_graylog 为 false 且 err_to_graylog 为 false , 表示不向远程的 graylog 服务输出任何日志;
  • log_graylog_host: 设置远程的 graylog 服务的地址;
  • log_graylog_port: 设置远程的 graylog 服务的端口;

journald 相关配置:

  • log_to_journald:
    • 是否将日志写入到 journald ,默认为 false ;
    • 如果为 true 表示将所有日志 (最高级别 99) 写入到 journald ;
  • err_to_journald:
    • 是否将异常日志写入到 journald ,默认为 false ;
    • 如果 log_to_journald 为 false 且 err_to_journald 为 true ,表示只将错误日志到 journald ;
    • 如果 log_to_journald 为 false 且 err_to_journald 为 false , 表示不向到 journald 写入任何日志;

1.3、日志相关操作

二、客户端侧日志

2.1、CephFS 客户端日志

2.1.1、Kernel 客户端日志

我的测试环境 Linux 内核版本较低,按照 Ceph 和 LinuxKernel 版本时间对照表 中的描述,我的内核版本 4.18.0 对应的内核中的 Ceph 版本应该是 v12.2.8 。

ceph 内核客户端使用的是动态调试 (Dynamic Debugging) 机制,可以通过 /sys/kernel/debug/dynamic_debug/control 文件来调整日志级别。

内核客户端日志相关命令:

# 查看当前 ceph 相关的内核模块调试设置
cat /sys/kernel/debug/dynamic_debug/control | grep ceph

# 启用 ceph 内核模块的调试日志
echo "module ceph +p" | sudo tee /sys/kernel/debug/dynamic_debug/control
echo "module libceph +p" | sudo tee /sys/kernel/debug/dynamic_debug/control

# 关闭 ceph 内核模块的调试日志
echo "module ceph -p" | sudo tee /sys/kernel/debug/dynamic_debug/control
echo "module libceph -p" | sudo tee /sys/kernel/debug/dynamic_debug/control

挂载相关命令:

# 挂载
mount -t ceph 10.1.1.10:6789,10.1.1.11:6789,10.1.1.12:6789:/ /mnt/cephfs -o name=admin,secret=AQBsU4RpxBrzDhAADU+H+7UQrHvZLYhsQFKTAw==

# 取消挂载
umount /mnt/cephfs

# 查看内核日志
dmesg -w | grep -i ceph
journalctl -f | grep -i ceph

2.1.2、Fuse 客户端日志

日志输出类别:

  • libfuse 日志:
    • 通过修改 /etc/ceph/ceph.conf 文件,在其中 [global] 字段中增加 fuse debug = true 配置,可以启用 libfuse 的 debug 模式;
    • 通过在使用 ceph-fuse 挂载的时候添加 -d 参数,可以启用 libfuse 的 debug 模式;
  • ceph 日志:
    • 通过修改 /etc/ceph/ceph.conf 文件,在其中 [client] 字段中增加 debug client = 20/20 配置,可以输出 ceph 的日志,日志文件位于 /var/log/ceph/ 目录;

相关命令:

# 挂载
ceph-fuse -n client.admin -m 10.1.1.10:6789,10.1.1.11:6789,10.1.1.12:6789 -r / /mnt/cephfs

# 挂载, 启用 debug 输出模式
ceph-fuse -n client.admin -m 10.1.1.10:6789,10.1.1.11:6789,10.1.1.12:6789 -r / /mnt/cephfs -d

# 取消挂载
fusermount -u /mnt/cephfs

Linux 命令与 Fuse OP 的对应关系:

  • 当前目录为 CephFS 的根目录,初始环境中只有一个名为 file 的文件和一个名为 dir 目录;
  • 在 Linux 命令执行过程中,有一些 OP 会执行多次,但是下面表格为了方便展示就只列出了一次,如果想要查看详细信息可查看下方的日志;
Linux 命令对应的 Fuse OP
lsOPENDIR, GETATTR, READDIR, RELEASEDIR
ls -alOPENDIR, GETATTR, READDIR, GETXATTR, LOOKUP, RELEASEDIR
touch file2LOOKUP, CREATE, FLUSH, SETATTR, RELEASE
mkdir dir2LOOKUP, MKDIR
cd dirGETATTR, LOOKUP, ACCESS
echo 123 > fileLOOKUP, OPEN, GETXATTR, FLUSH, WRITE, FLUSH, RELEASE
cat fileGETATTR, LOOKUP, OPEN, GETATTR, READ, GETATTR, FLUSH, RELEASE
cp file file3GETATTR, LOOKUP, OPEN, CREATE, GETXATTR, WRITE, FLUSH, RELEASE
mv file3 file4GETATTR, LOOKUP, RENAME
mv dir dir3LOOKUP, RENAME
ln -s file4 file5GETATTR, LOOKUP, SYMLINK
ln file5 file6LOOKUP, LINK
unlink file6GETATTR, LOOKUP, UNLINK

Linux 命令与 Fuse OP 的对应关系的相关日志:

# ls
unique: 272, opcode: OPENDIR (27), nodeid: 1, insize: 48, pid: 494375
unique: 272, success, outsize: 32
unique: 274, opcode: GETATTR (3), nodeid: 1, insize: 56, pid: 494375
unique: 274, success, outsize: 120
unique: 276, opcode: READDIR (28), nodeid: 1, insize: 80, pid: 494375
unique: 276, success, outsize: 144
unique: 278, opcode: READDIR (28), nodeid: 1, insize: 80, pid: 494375
unique: 278, success, outsize: 16
unique: 280, opcode: RELEASEDIR (29), nodeid: 1, insize: 64, pid: 0
unique: 280, success, outsize: 16

# ls -al
unique: 282, opcode: OPENDIR (27), nodeid: 1, insize: 48, pid: 494478
unique: 282, success, outsize: 32
unique: 284, opcode: GETATTR (3), nodeid: 1, insize: 56, pid: 494478
unique: 284, success, outsize: 120
unique: 286, opcode: READDIR (28), nodeid: 1, insize: 80, pid: 494478
unique: 286, success, outsize: 144
unique: 288, opcode: GETATTR (3), nodeid: 1, insize: 56, pid: 494478
unique: 288, success, outsize: 120
unique: 290, opcode: GETXATTR (22), nodeid: 1, insize: 65, pid: 494478
unique: 290, error: -61 (No data available), outsize: 16
unique: 292, opcode: GETXATTR (22), nodeid: 1, insize: 72, pid: 494478
unique: 292, error: -95 (Operation not supported), outsize: 16
unique: 294, opcode: GETXATTR (22), nodeid: 1, insize: 64, pid: 494478
unique: 294, error: -95 (Operation not supported), outsize: 16
unique: 296, opcode: LOOKUP (1), nodeid: 1, insize: 45, pid: 494478
unique: 296, success, outsize: 144
unique: 298, opcode: GETATTR (3), nodeid: 1099511627779, insize: 56, pid: 494478
unique: 298, success, outsize: 120
unique: 300, opcode: LOOKUP (1), nodeid: 1, insize: 45, pid: 494478
unique: 300, success, outsize: 144
unique: 302, opcode: GETXATTR (22), nodeid: 1099511627779, insize: 65, pid: 494478
unique: 302, error: -61 (No data available), outsize: 16
unique: 304, opcode: LOOKUP (1), nodeid: 1, insize: 44, pid: 494478
unique: 304, success, outsize: 144
unique: 306, opcode: GETATTR (3), nodeid: 1099511628280, insize: 56, pid: 494478
unique: 306, success, outsize: 120
unique: 308, opcode: LOOKUP (1), nodeid: 1, insize: 44, pid: 494478
unique: 308, success, outsize: 144
unique: 310, opcode: GETXATTR (22), nodeid: 1099511628280, insize: 65, pid: 494478
unique: 310, error: -61 (No data available), outsize: 16
unique: 312, opcode: READDIR (28), nodeid: 1, insize: 80, pid: 494478
unique: 312, success, outsize: 16
unique: 314, opcode: RELEASEDIR (29), nodeid: 1, insize: 64, pid: 0
unique: 314, success, outsize: 16

# touch file2
unique: 316, opcode: LOOKUP (1), nodeid: 1, insize: 46, pid: 495062
unique: 316, error: -2 (No such file or directory), outsize: 16
unique: 318, opcode: CREATE (35), nodeid: 1, insize: 62, pid: 495062
unique: 318, success, outsize: 160
unique: 320, opcode: FLUSH (25), nodeid: 1099511628281, insize: 64, pid: 495062
unique: 320, success, outsize: 16
unique: 322, opcode: SETATTR (4), nodeid: 1099511628281, insize: 128, pid: 495062
unique: 322, success, outsize: 120
unique: 324, opcode: FLUSH (25), nodeid: 1099511628281, insize: 64, pid: 495062
unique: 324, success, outsize: 16
unique: 326, opcode: RELEASE (18), nodeid: 1099511628281, insize: 64, pid: 0
unique: 326, success, outsize: 16

# mkdir dir2
unique: 328, opcode: LOOKUP (1), nodeid: 1, insize: 45, pid: 495115
unique: 328, error: -2 (No such file or directory), outsize: 16
unique: 330, opcode: MKDIR (9), nodeid: 1, insize: 53, pid: 495115
unique: 330, success, outsize: 144

# cd dir
unique: 354, opcode: GETATTR (3), nodeid: 1, insize: 56, pid: 491208
unique: 354, success, outsize: 120
unique: 356, opcode: LOOKUP (1), nodeid: 1, insize: 44, pid: 491208
unique: 356, success, outsize: 144
unique: 358, opcode: GETATTR (3), nodeid: 1099511628280, insize: 56, pid: 491208
unique: 358, success, outsize: 120
unique: 360, opcode: LOOKUP (1), nodeid: 1, insize: 44, pid: 491208
unique: 360, success, outsize: 144
unique: 362, opcode: ACCESS (34), nodeid: 1099511628280, insize: 48, pid: 491208
unique: 362, success, outsize: 16

# echo 123 > file
unique: 376, opcode: LOOKUP (1), nodeid: 1, insize: 45, pid: 491208
unique: 376, success, outsize: 144
unique: 378, opcode: OPEN (14), nodeid: 1099511627779, insize: 48, pid: 491208
unique: 378, success, outsize: 32
unique: 380, opcode: GETXATTR (22), nodeid: 1099511627779, insize: 68, pid: 491208
unique: 380, error: -61 (No data available), outsize: 16
unique: 382, opcode: FLUSH (25), nodeid: 1099511627779, insize: 64, pid: 491208
unique: 382, success, outsize: 16
unique: 384, opcode: GETXATTR (22), nodeid: 1099511627779, insize: 68, pid: 491208
unique: 384, error: -61 (No data available), outsize: 16
unique: 386, opcode: WRITE (16), nodeid: 1099511627779, insize: 84, pid: 491208
unique: 386, success, outsize: 24
unique: 388, opcode: FLUSH (25), nodeid: 1099511627779, insize: 64, pid: 491208
unique: 388, success, outsize: 16
unique: 390, opcode: RELEASE (18), nodeid: 1099511627779, insize: 64, pid: 0
unique: 390, success, outsize: 16

# cat file
unique: 406, opcode: GETATTR (3), nodeid: 1, insize: 56, pid: 491208
unique: 406, success, outsize: 120
unique: 408, opcode: LOOKUP (1), nodeid: 1, insize: 45, pid: 495374
unique: 408, success, outsize: 144
unique: 410, opcode: OPEN (14), nodeid: 1099511627779, insize: 48, pid: 495374
unique: 410, success, outsize: 32
unique: 412, opcode: GETATTR (3), nodeid: 1099511627779, insize: 56, pid: 495374
unique: 412, success, outsize: 120
unique: 414, opcode: GETATTR (3), nodeid: 1099511627779, insize: 56, pid: 495374
unique: 414, success, outsize: 120
unique: 416, opcode: READ (15), nodeid: 1099511627779, insize: 80, pid: 495374
unique: 416, success, outsize: 24
unique: 418, opcode: GETATTR (3), nodeid: 1099511627779, insize: 56, pid: 495374
unique: 418, success, outsize: 120
unique: 420, opcode: FLUSH (25), nodeid: 1099511627779, insize: 64, pid: 495374
unique: 420, success, outsize: 16
unique: 422, opcode: RELEASE (18), nodeid: 1099511627779, insize: 64, pid: 0
unique: 422, success, outsize: 16

# cp file file3
unique: 474, opcode: GETATTR (3), nodeid: 1, insize: 56, pid: 491208
unique: 474, success, outsize: 120
unique: 476, opcode: LOOKUP (1), nodeid: 1, insize: 46, pid: 495447
unique: 476, error: -2 (No such file or directory), outsize: 16
unique: 478, opcode: LOOKUP (1), nodeid: 1, insize: 45, pid: 495447
unique: 478, success, outsize: 144
unique: 480, opcode: GETATTR (3), nodeid: 1099511627779, insize: 56, pid: 495447
unique: 480, success, outsize: 120
unique: 482, opcode: LOOKUP (1), nodeid: 1, insize: 46, pid: 495447
unique: 482, error: -2 (No such file or directory), outsize: 16
unique: 484, opcode: LOOKUP (1), nodeid: 1, insize: 45, pid: 495447
unique: 484, success, outsize: 144
unique: 486, opcode: OPEN (14), nodeid: 1099511627779, insize: 48, pid: 495447
unique: 486, success, outsize: 32
unique: 488, opcode: GETATTR (3), nodeid: 1099511627779, insize: 56, pid: 495447
unique: 488, success, outsize: 120
unique: 490, opcode: LOOKUP (1), nodeid: 1, insize: 46, pid: 495447
unique: 490, error: -2 (No such file or directory), outsize: 16
unique: 492, opcode: CREATE (35), nodeid: 1, insize: 62, pid: 495447
unique: 492, success, outsize: 160
unique: 494, opcode: GETATTR (3), nodeid: 1099511628283, insize: 56, pid: 495447
unique: 494, success, outsize: 120
unique: 496, opcode: GETATTR (3), nodeid: 1099511627779, insize: 56, pid: 495447
unique: 496, success, outsize: 120
unique: 498, opcode: GETXATTR (22), nodeid: 1099511628283, insize: 68, pid: 495447
unique: 498, error: -61 (No data available), outsize: 16
unique: 500, opcode: WRITE (16), nodeid: 1099511628283, insize: 88, pid: 495447
unique: 500, success, outsize: 24
unique: 502, opcode: GETATTR (3), nodeid: 1099511627779, insize: 56, pid: 495447
unique: 502, success, outsize: 120
unique: 504, opcode: FLUSH (25), nodeid: 1099511628283, insize: 64, pid: 495447
unique: 504, success, outsize: 16
unique: 506, opcode: RELEASE (18), nodeid: 1099511628283, insize: 64, pid: 0
unique: 508, opcode: FLUSH (25), nodeid: 1099511627779, insize: 64, pid: 495447
unique: 506, success, outsize: 16
unique: 508, success, outsize: 16
unique: 510, opcode: RELEASE (18), nodeid: 1099511627779, insize: 64, pid: 0
unique: 510, success, outsize: 16

# mv file3 file4
unique: 570, opcode: GETATTR (3), nodeid: 1, insize: 56, pid: 491208
unique: 570, success, outsize: 120
unique: 572, opcode: LOOKUP (1), nodeid: 1, insize: 46, pid: 495588
unique: 572, success, outsize: 144
unique: 574, opcode: LOOKUP (1), nodeid: 1, insize: 46, pid: 495588
unique: 574, error: -2 (No such file or directory), outsize: 16
unique: 576, opcode: LOOKUP (1), nodeid: 1, insize: 46, pid: 495588
unique: 576, error: -2 (No such file or directory), outsize: 16
unique: 578, opcode: LOOKUP (1), nodeid: 1, insize: 46, pid: 495588
unique: 578, success, outsize: 144
unique: 580, opcode: LOOKUP (1), nodeid: 1, insize: 46, pid: 495588
unique: 580, error: -2 (No such file or directory), outsize: 16
unique: 582, opcode: RENAME (12), nodeid: 1, insize: 60, pid: 495588
unique: 582, success, outsize: 16

# mv dir dir3
unique: 696, opcode: LOOKUP (1), nodeid: 1, insize: 44, pid: 495657
unique: 696, success, outsize: 144
unique: 698, opcode: LOOKUP (1), nodeid: 1, insize: 45, pid: 495657
unique: 698, error: -2 (No such file or directory), outsize: 16
unique: 700, opcode: LOOKUP (1), nodeid: 1, insize: 45, pid: 495657
unique: 700, error: -2 (No such file or directory), outsize: 16
unique: 702, opcode: LOOKUP (1), nodeid: 1, insize: 44, pid: 495657
unique: 702, success, outsize: 144
unique: 704, opcode: LOOKUP (1), nodeid: 1, insize: 45, pid: 495657
unique: 704, error: -2 (No such file or directory), outsize: 16
unique: 706, opcode: RENAME (12), nodeid: 1, insize: 57, pid: 495657
unique: 706, success, outsize: 16

# ln -s file4 file5
unique: 708, opcode: GETATTR (3), nodeid: 1, insize: 56, pid: 491208
unique: 708, success, outsize: 120
unique: 710, opcode: LOOKUP (1), nodeid: 1, insize: 46, pid: 495742
unique: 710, error: -2 (No such file or directory), outsize: 16
unique: 712, opcode: SYMLINK (6), nodeid: 1, insize: 52, pid: 495742
unique: 712, success, outsize: 144

# ln file5 file6
unique: 784, opcode: LOOKUP (1), nodeid: 1, insize: 46, pid: 495813
unique: 784, success, outsize: 144
unique: 786, opcode: LOOKUP (1), nodeid: 1, insize: 46, pid: 495813
unique: 786, error: -2 (No such file or directory), outsize: 16
unique: 788, opcode: LINK (13), nodeid: 1, insize: 54, pid: 495813
unique: 788, success, outsize: 144

# unlink file6
unique: 790, opcode: GETATTR (3), nodeid: 1, insize: 56, pid: 491208
unique: 790, success, outsize: 120
unique: 792, opcode: LOOKUP (1), nodeid: 1, insize: 46, pid: 495869
unique: 792, success, outsize: 144
unique: 794, opcode: UNLINK (10), nodeid: 1, insize: 46, pid: 495869
unique: 794, success, outsize: 16

2.2、CephRBD 客户端日志

相关命令:

# 创建并初始化 rbd pool
ceph osd pool create rbd-pool
rbd pool init rbd-pool
ceph osd pool application enable rbd-pool rbd

# 创建 rbd 镜像
rbd create rbd-pool/image1 --size 10G
rbd ls rbd-pool
rbd info rbd-pool/image1

2.2.1、Kernel 客户端日志

我的测试环境 Linux 内核版本较低,按照 Ceph 和 LinuxKernel 版本时间对照表 中的描述,我的内核版本 4.18.0 对应的内核中的 Ceph 版本应该是 v12.2.8 。

rbd 内核客户端使用的是动态调试 (Dynamic Debugging) 机制,可以通过 /sys/kernel/debug/dynamic_debug/control 文件来调整日志级别。

内核客户端日志相关命令:

# 查看当前 ceph 相关的内核模块调试设置
cat /sys/kernel/debug/dynamic_debug/control | grep ceph

# 启用 ceph 内核模块的调试日志
echo "module rbd +p" | sudo tee /sys/kernel/debug/dynamic_debug/control
echo "module libceph +p" | sudo tee /sys/kernel/debug/dynamic_debug/control

# 关闭 ceph 内核模块的调试日志
echo "module rbd -p" | sudo tee /sys/kernel/debug/dynamic_debug/control
echo "module libceph -p" | sudo tee /sys/kernel/debug/dynamic_debug/control

相关命令:

# 挂载 rbd 镜像
rbd device map rbd-pool/image1
mkfs.xfs /dev/rbd0
mkdir -p /mnt/cephrbd
mount /dev/rbd0 /mnt/cephrbd

# 查看挂载的 image
rbd device ls

# 取消挂载 rbd 镜像
umount /mnt/cephrbd
rbd device unmap rbd-pool/image1

2.2.2、Fuse 客户端日志

rbd-fuse 命令也可以使用 -d 参数来启用 debug 输出模式,但是实际测试发现 rbd-fuse 即使启用了 -d 参数,也没有额外的日志输出。

相关命令:

# 安装依赖软件
dnf install rbd-fuse

# 挂载 rbd 镜像
mkdir -p /mnt/cephrbd-fuse
rbd-fuse /mnt/cephrbd-fuse -c /etc/ceph/ceph.conf -p rbd-pool -r image1
# rbd-fuse /mnt/cephrbd-fuse -c /etc/ceph/ceph.conf -p rbd-pool -r image1 -d
mkfs.xfs /mnt/cephrbd-fuse/image1
mkdir -p /mnt/cephrbd-image1
mount -o loop /mnt/cephrbd-fuse/image1 /mnt/cephrbd-image1
df -h /mnt/cephrbd-image1

# 取消挂载 rbd 镜像
umount /mnt/cephrbd-image1
fusermount -u /mnt/cephrbd-fuse