Ceph 日志实现分析
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() )能够查看最近的日志历史;
- 但是只有在 日志级别 范围内的日志才会输出到日志中;
| 子系统 | 日志级别 | 内存级别 |
|---|---|---|
| default | 0 | 5 |
| lockdep | 0 | 1 |
| context | 0 | 1 |
| crush | 1 | 1 |
| mds | 1 | 5 |
| … | … | … |
设置对应日志级别的函数定义:
- 从下面可以看出,第一个值对应的是 log_level , 第二个值对应的是 gather_level ;
- 实际设置的 m_gather_levels[subsys] 的值为 log_level 和 gather_level 的较大值;
|
日志子系统的枚举定义:
|
日志子系统的数组定义:
|
日志子系统的最长名称定义: (目前该函数没有被实际使用。)
|
1.1.2、日志输出宏定义
日志输出示例:
|
相关宏:
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) 宏定义中使用的宏;
相关宏定义代码:
|
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 线程流程:
|
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; ,实际在运行的时候会被替换为如下的示例代码:
|
注意: 日志在提交前会调用 should_gather 函数来判断当前的日志级别是否符合当前子系统的约束,否则不会将对应的日志提交到日志队列中。
|
之后就走到了 Log::submit_entry 函数的执行逻辑中,注意: 如果日志队列中的元素超过 m_max_new (对应 log_max_new 参数, 默认为 1000) ,则线程会阻塞等待,直到队列有空闲位置。当将需要记录的日志提交到 m_new 中之后,就会通过条件变量来通知 log 线程处理日志。
|
1.2.2、日志消费者
按照之前的描述,每个服务都有一个处理日志的名为 log 的线程,对应的线程入口函数为 Log::entry ,其内部关键的写日志的函数为 Log::_flush 。分析其具体实现,我们发现 ceph 支持将日志存储到五个地方,分别如下:
| 输出目标 | 正常日志阈值 | 崩溃日志阈值 | 是否默认启用 | 启用方法 |
|---|---|---|---|---|
| file | 子系统级别 | 子系统级别 | 是(如果设置文件) | set_log_file() |
| stderr | m_stderr_log | m_stderr_crash | 是 | set_stderr_level() |
| syslog | m_syslog_log | m_syslog_crash | 否 | set_syslog_level() |
| graylog | m_graylog_log | m_graylog_crash | 否 | start_graylog() |
| journald | m_journald_log | m_journald_crash | 否 | start_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 文件来调整日志级别。
内核客户端日志相关命令:
|
挂载相关命令:
|
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/目录;
- 通过修改
相关命令:
|
Linux 命令与 Fuse OP 的对应关系:
- 当前目录为 CephFS 的根目录,初始环境中只有一个名为 file 的文件和一个名为 dir 目录;
- 在 Linux 命令执行过程中,有一些 OP 会执行多次,但是下面表格为了方便展示就只列出了一次,如果想要查看详细信息可查看下方的日志;
| Linux 命令 | 对应的 Fuse OP |
|---|---|
| ls | OPENDIR, GETATTR, READDIR, RELEASEDIR |
| ls -al | OPENDIR, GETATTR, READDIR, GETXATTR, LOOKUP, RELEASEDIR |
| touch file2 | LOOKUP, CREATE, FLUSH, SETATTR, RELEASE |
| mkdir dir2 | LOOKUP, MKDIR |
| cd dir | GETATTR, LOOKUP, ACCESS |
| echo 123 > file | LOOKUP, OPEN, GETXATTR, FLUSH, WRITE, FLUSH, RELEASE |
| cat file | GETATTR, LOOKUP, OPEN, GETATTR, READ, GETATTR, FLUSH, RELEASE |
| cp file file3 | GETATTR, LOOKUP, OPEN, CREATE, GETXATTR, WRITE, FLUSH, RELEASE |
| mv file3 file4 | GETATTR, LOOKUP, RENAME |
| mv dir dir3 | LOOKUP, RENAME |
| ln -s file4 file5 | GETATTR, LOOKUP, SYMLINK |
| ln file5 file6 | LOOKUP, LINK |
| unlink file6 | GETATTR, LOOKUP, UNLINK |
Linux 命令与 Fuse OP 的对应关系的相关日志:
|
2.2、CephRBD 客户端日志
相关命令:
|
2.2.1、Kernel 客户端日志
我的测试环境 Linux 内核版本较低,按照 Ceph 和 LinuxKernel 版本时间对照表 中的描述,我的内核版本 4.18.0 对应的内核中的 Ceph 版本应该是 v12.2.8 。
rbd 内核客户端使用的是动态调试 (Dynamic Debugging) 机制,可以通过 /sys/kernel/debug/dynamic_debug/control 文件来调整日志级别。
内核客户端日志相关命令:
|
相关命令:
|
2.2.2、Fuse 客户端日志
rbd-fuse 命令也可以使用 -d 参数来启用 debug 输出模式,但是实际测试发现 rbd-fuse 即使启用了 -d 参数,也没有额外的日志输出。
相关命令:
|


