在 BlueZ 5.x 的架构中,log 模块是整个蓝牙守护进程(
bluetoothd)的"听诊器"。它不仅承担着传统的调试输出功能,还实现了基于 HCI 日志通道的远程日志传输,让开发者可以通过btmon工具实时捕获蓝牙协议栈的运行时日志。这种双路日志输出架构(syslog + HCI Logging Channel)使得 BlueZ 在嵌入式蓝牙开发场景下具有极强的可观测性。
目录
[一、log 模块整体架构](#一、log 模块整体架构)
[四、HCI 日志传输协议](#四、HCI 日志传输协议)
[八、btmon 日志接收与显示](#八、btmon 日志接收与显示)
本文基于 BlueZ 5.x 完整源码,深度剖析 log 模块的实现机制,覆盖核心源码文件包括:
-
src/log.h --- 日志接口定义与调试描述符结构
-
src/log.c --- 日志核心实现(syslog 输出 + 调试控制)
-
src/shared/log.h --- HCI 日志通道 API
-
src/shared/log.c --- HCI 日志传输实现
-
src/main.c --- 日志初始化与运行时控制
一、 log 模块整体架构
1.1 双路日志输出架构
BlueZ 采用创新的双路日志输出架构:
cpp
┌─────────────────────────────────────────────────────────────────────┐
│ bluetoothd 进程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 业务模块 (adapter.c, device.c, agent.c 等) │
│ │ │
│ ▼ │
│ 日志宏接口 (DBG/error/warn/info) │
│ │ │
│ ├──► syslog (本地系统日志) │
│ │ └──► /var/log/syslog 或 journald │
│ │ │
│ └──► HCI Logging Channel (远程日志传输) │
│ └──► btmon 实时捕获与显示 │
│ │
└─────────────────────────────────────────────────────────────────────┘
|---------------------------------|---------------------------------|-----------|
| 输出路径 | 实现机制 | 适用场景 |
| syslog | vsyslog() 系统调用 | 系统级日志持久化 |
| HCI Logging Channel | PF_BLUETOOTH socket + sendmsg() | 实时调试、协议分析 |
1.2 日志分级体系
BlueZ 采用标准的 syslog 优先级分级:
cpp
// syslog 标准优先级
LOG_EMERG (0) // 紧急:系统不可用
LOG_ALERT (1) // 警报:立即需要修复
LOG_CRIT (2) // 严重:关键错误
LOG_ERR (3) // 错误:需要关注
LOG_WARNING (4) // 警告:可能的问题
LOG_NOTICE (5) // 通知:正常但重要
LOG_INFO (6) // 信息:常规信息
LOG_DEBUG (7) // 调试:详细调试信息
1.3 核心数据结构 btd_debug_desc
每个调试日志点都通过 struct btd_debug_desc 描述(定义于 log.h):
cpp
struct btd_debug_desc {
const char *file; // 源文件路径
#define BTD_DEBUG_FLAG_DEFAULT (0) // 默认未启用
#define BTD_DEBUG_FLAG_PRINT (1 << 0) // 启用打印标志
unsigned int flags; // 调试标志位
} __attribute__((aligned(8)));
关键设计:
-
每个
DBG()宏调用都会创建一个静态的btd_debug_desc变量 -
使用
section("__debug")属性将所有描述符链接到 ELF 的__debug段 -
运行时通过遍历
__start___debug到__stop___debug来发现所有调试点
二、日志打印接口实现
2.1 核心日志函数
BlueZ 提供了六个核心日志函数(定义于 log.c):
info():通用信息日志
cpp
// log.c:43
void info(const char *format, ...)
{
va_list ap;
// 输出到 syslog
va_start(ap, format);
vsyslog(LOG_INFO, format, ap);
va_end(ap);
// 输出到 HCI Logging Channel
va_start(ap, format);
monitor_log(HCI_DEV_NONE, LOG_INFO, format, ap);
va_end(ap);
}
特点:
-
无控制器索引参数,使用
HCI_DEV_NONE -
固定优先级为
LOG_INFO -
适用于全局非控制器相关的日志
btd_debug():带索引的调试日志
cpp
// log.c:108
void btd_debug(uint16_t index, const char *format, ...)
{
va_list ap;
va_start(ap, format);
vsyslog(LOG_DEBUG, format, ap);
va_end(ap);
va_start(ap, format);
monitor_log(index, LOG_DEBUG, format, ap);
va_end(ap);
}
特点:
-
支持控制器索引参数,可通过
-i选项过滤 -
固定优先级为
LOG_DEBUG -
输出到 syslog 和 HCI 双通道
其他日志函数
cpp
// 带索引的通用日志(可变优先级)
void btd_log(uint16_t index, int priority, const char *format, ...);
// 错误日志
void btd_error(uint16_t index, const char *format, ...);
// 警告日志
void btd_warn(uint16_t index, const char *format, ...);
// 信息日志
void btd_info(uint16_t index, const char *format, ...);
2.2 monitor_log():HCI 日志通道转发
所有日志函数最终都会调用 monitor_log()(log.c:):
cpp
// log.c:37
static void monitor_log(uint16_t index, int priority,
const char *format, va_list ap)
{
// 通过 bt_log_vprintf() 发送到 HCI Logging Channel
bt_log_vprintf(index, LOG_IDENT, priority, format, ap);
}
其中 LOG_IDENT 定义为 "bluetoothd"(log.c,作为日志标识。
三、调试描述符机制
3.1 DBG 宏:自动注册调试点
DBG 宏是 BlueZ 日志系统的核心创新(log.h):
cpp
// log.h:49-56
#define DBG_IDX(idx, fmt, arg...) do { \
static struct btd_debug_desc __btd_debug_desc \
__attribute__((used, section("__debug"), aligned(8))) = { \
.file = __FILE__, .flags = BTD_DEBUG_FLAG_DEFAULT, \
}; \
if (__btd_debug_desc.flags & BTD_DEBUG_FLAG_PRINT) \
btd_debug(idx, "%s:%s() " fmt, __FILE__, __func__ , ## arg); \
} while (0)
#define DBG(fmt, arg...) DBG_IDX(0xffff, fmt, ## arg)
工作原理:
-
静态变量创建:每个 DBG() 调用都定义一个唯一的静态 btd_debug_desc 变量
-
段属性定位:使用 section("__debug") 将变量放入 ELF 的特殊段
-
条件编译:运行时检查 BTD_DEBUG_FLAG_PRINT 标志决定是否输出
-
自动格式化:自动附加 FILE 和 func 信息
3.2 日志调用示例
在 adapter.c中可以看到典型的 DBG 使用:
cpp
// adapter.c:392
DBG("Class: 0x%06x", dev_class);
// adapter.c:570-571
DBG("Changed settings: 0x%08x", changed_mask);
DBG("Pending settings: 0x%08x", adapter->pending_settings);
编译后,每个 DBG() 宏调用会生成:
-
一个静态的
btd_debug_desc变量(存入.debug段) -
条件性的
btd_debug()函数调用
3.3 错误和警告宏
cpp
// log.h:59-62
#define error(fmt, arg...) \
btd_error(0xffff, "%s:%s() " fmt, __FILE__, __func__, ## arg)
#define warn(fmt, arg...) \
btd_warn(0xffff, "%s:%s() " fmt, __FILE__, __func__, ## arg)
特点:
-
error()和warn()总是输出(无条件判断) -
自动包含文件名和函数名
-
使用固定索引
0xffff(HCI_DEV_NONE)
四、HCI 日志传输协议
4.1 日志数据包格式
HCI 日志通道使用自定义的二进制协议(定义于 src/shared/log.c):
cpp
// src/shared/log.c:32-38
struct log_hdr {
uint16_t opcode; // 操作码 (固定为 0x0000)
uint16_t index; // 控制器索引
uint16_t len; // 数据长度
uint8_t priority; // 日志优先级
uint8_t ident_len; // 标识长度
} __attribute__((packed));
数据包结构:

4.2 bt_log_open():建立 HCI 日志通道
bt_log_open() 创建并绑定 HCI 日志 socket:
cpp
// src/shared/log.c:95-128
int bt_log_open(void)
{
struct sockaddr_hci addr;
int fd;
static int err;
if (err < 0)
return err;
if (log_fd >= 0)
return log_fd;
// 创建 HCI socket
fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
if (fd < 0) {
err = -errno;
return -errno;
}
// 绑定到 HCI_LOGGING 通道
memset(&addr, 0, sizeof(addr));
addr.hci_family = AF_BLUETOOTH;
addr.hci_dev = HCI_DEV_NONE; // 不绑定特定控制器
addr.hci_channel = HCI_CHANNEL_LOGGING; // 日志通道 (值为 4)
err = bind(fd, (struct sockaddr *) &addr, sizeof(addr));
if (err < 0) {
err = -errno;
close(fd);
return err;
}
log_fd = fd;
return fd;
}
关键步骤:
-
创建
PF_BLUETOOTH域的原始 socket -
绑定到
HCI_CHANNEL_LOGGING通道(值为 4) -
不绑定特定设备(
HCI_DEV_NONE),日志对所有控制器可见
4.3 bt_log_sendmsg():发送日志数据
bt_log_sendmsg() 使用 sendmsg() 发送完整的日志数据包:
cpp
// src/shared/log.c:47-93
int bt_log_sendmsg(uint16_t index, const char *label, int level,
struct iovec *io, size_t io_len)
{
struct log_hdr hdr;
struct msghdr msg;
struct iovec iov[5];
size_t i;
int err;
if (io_len > 3)
return -EMSGSIZE;
log_fd = bt_log_open();
if (log_fd < 0)
return log_fd;
// 填充日志头
hdr.opcode = cpu_to_le16(0x0000);
hdr.index = cpu_to_le16(index);
hdr.ident_len = strlen(label) + 1;
hdr.len = cpu_to_le16(2 + hdr.ident_len);
hdr.priority = level;
// 构建 scatter-gather I/O
iov[0].iov_base = &hdr;
iov[0].iov_len = sizeof(hdr);
iov[1].iov_base = (void *) label;
iov[1].iov_len = hdr.ident_len;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = iov;
msg.msg_iovlen = 2;
// 添加额外的数据块
for (i = 0; i < io_len; i++) {
iov[i + 2] = io[i];
hdr.len += io[i].iov_len;
msg.msg_iovlen++;
}
// 发送数据
err = sendmsg(log_fd, &msg, 0);
if (err < 0) {
err = -errno;
close(log_fd);
log_fd = -1;
}
return err;
}
性能优化:
-
使用 scatter-gather I/O 减少系统调用次数
-
失败时自动关闭连接并重置状态
-
支持最多 3 个额外数据块(
io_len <= 3)
4.4 bt_log_vprintf():格式化日志
bt_log_vprintf()格式化并发送日志消息:
cpp
// src/shared/log.c:130-157
int bt_log_vprintf(uint16_t index, const char *label, int level,
const char *format, va_list ap)
{
struct iovec iov;
char *str;
int len;
// 格式化字符串
len = vasprintf(&str, format, ap);
if (len < 0)
return errno;
len = strlen(str);
// 移除末尾的换行符(btmon 会自动添加)
if (len > 1 && str[len - 1] == '\n') {
str[len - 1] = '\0';
len--;
}
// 构建 I/O 向量并发送
iov.iov_base = str;
iov.iov_len = len + 1;
len = bt_log_sendmsg(index, label, level, &iov, 1);
free(str);
return len;
}
五、日志初始化流程
5.1 __btd_log_init():日志系统初始化
__btd_log_init() 是日志系统的入口函数:
cpp
// log.c:163-180
void __btd_log_init(const char *debug, int detach)
{
int option = LOG_NDELAY | LOG_PID;
// 1. 解析调试选项
if (debug != NULL)
enabled = g_strsplit_set(debug, ":, ", 0);
// 2. 启用匹配的调试点
__btd_enable_debug(__start___debug, __stop___debug);
// 3. 打开 HCI 日志通道
bt_log_open();
// 4. 设置 syslog 选项
if (!detach)
option |= LOG_PERROR; // 非守护模式下同时输出到 stderr
// 5. 打开 syslog
openlog(LOG_IDENT, option, LOG_DAEMON);
// 6. 输出启动日志
info("Bluetooth daemon %s", VERSION);
}
初始化步骤:
-
解析调试过滤字符串(支持
:,,,分隔符) -
遍历所有调试描述符,启用匹配的调试点
-
打开 HCI 日志通道用于远程日志传输
-
根据
detach参数决定是否同时输出到 stderr -
打开 syslog,设置标识符为
"bluetoothd" -
输出启动信息日志
5.2 main() 中的调用
在 main.c:1189 中调用日志初始化:
cpp
// main.c:1189
__btd_log_init(option_debug, option_detach);
5.3 选项解析
option_debug:调试过滤字符串
cpp
// main.c:1102-1111
static gboolean parse_debug(const char *key, const char *value,
gpointer user_data, GError **error)
{
if (value)
option_debug = g_strdup(value);
else
option_debug = g_strdup("*"); // 无参数时启用所有调试
return TRUE;
}
支持格式:
-
*--- 启用所有调试日志 -
adapter.c--- 仅启用 adapter.c 的调试 -
adapter.c:device.c--- 启用多个文件的调试 -
adapter.c,device.c--- 使用逗号分隔 -
adapter.c device.c--- 使用空格分隔
option_detach:守护进程模式
cpp
// main.c:1040
static gboolean option_detach = TRUE; // 默认守护进程模式
-
TRUE:后台运行,日志仅输出到 syslog -
FALSE:前台运行,日志同时输出到 stderr
六、调试过滤机制
6.1 is_enabled():匹配调试描述符
is_enabled()判断某个调试描述符是否应该启用:
cpp
// log.c:126-139
static gboolean is_enabled(struct btd_debug_desc *desc)
{
int i;
if (enabled == NULL)
return 0; // 未设置调试过滤时不启用
for (i = 0; enabled[i] != NULL; i++)
// 使用 GPattern 模式匹配
if (desc->file != NULL && g_pattern_match_simple(enabled[i],
desc->file) == TRUE)
return 1;
return 0;
}
匹配规则:
-
使用
g_pattern_match_simple()进行 glob 模式匹配 -
*匹配所有文件 -
adapter.c仅匹配 adapter.c -
adapter*匹配所有以 adapter 开头的文件
6.2 __btd_enable_debug():批量启用调试
__btd_enable_debug()遍历指定范围的调试描述符:
cpp
// log.c:141-153
void __btd_enable_debug(struct btd_debug_desc *start,
struct btd_debug_desc *stop)
{
struct btd_debug_desc *desc;
if (start == NULL || stop == NULL)
return;
for (desc = start; desc < stop; desc++) {
if (is_enabled(desc))
desc->flags |= BTD_DEBUG_FLAG_PRINT;
}
}
6.3 __btd_toggle_debug():强制启用所有调试
__btd_toggle_debug()强制启用所有调试点:
cpp
// log.c:155-161
void __btd_toggle_debug(void)
{
struct btd_debug_desc *desc;
for (desc = __start___debug; desc < __stop___debug; desc++)
desc->flags |= BTD_DEBUG_FLAG_PRINT;
}
用途:响应 SIGUSR2信号,在运行时动态启用所有调试日志。
七、运行时日志控制
7.1 SIGUSR2 信号处理
在 main.c中处理 SIGUSR2 信号:
cpp
// main.c:1027-1031
case SIGUSR2:
__btd_toggle_debug(); // 启用所有调试日志
break;
使用方法:
cpp
# 发送 SIGUSR2 信号给 bluetoothd
kill -USR2 $(pidof bluetoothd)
7.2 GLib 日志处理
在 main.c 中设置 GLib 日志处理器:
cpp
// main.c:1191-1193
g_log_set_handler("GLib", G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL |
G_LOG_FLAG_RECURSION,
log_handler, NULL);
7.3 日志清理
__btd_log_cleanup()关闭日志系统:
cpp
// log.c:182-189
void __btd_log_cleanup(void)
{
closelog(); // 关闭 syslog
bt_log_close(); // 关闭 HCI 日志通道
g_strfreev(enabled); // 释放调试过滤列表
}
八、btmon 日志接收与显示
8.1 btmon 接收日志
btmon 通过监听 HCI 日志通道接收 bluetoothd 发送的日志数据包。当收到 BTSNOOP_OPCODE_USER_LOGGING 操作码的数据包时进行解析。
8.2 数据包解析
在 monitor/packet.c中处理用户日志:
cpp
// monitor/packet.c:4051-4058
case BTSNOOP_OPCODE_USER_LOGGING:
ul = data;
ident = ul->ident_len ? data + sizeof(*ul) : NULL;
packet_user_logging(tv, cred, index, ul->priority, ident,
data + sizeof(*ul) + ul->ident_len,
size - (sizeof(*ul) + ul->ident_len));
break;
8.3 使用 btmon 调试
cpp
# 启动 btmon 实时捕获日志
sudo btmon
# 启动 bluetoothd 并启用调试
sudo bluetoothd -d
sudo bluetoothd -d '*' # 所有模块调试
sudo bluetoothd -d 'adapter.c' # 仅 adapter 模块调试
# 控制器过滤
sudo bluetoothd -d -i 0 # 仅显示 hci0 的日志
九、日志环境变量与配置
9.1 编译时调试开关
cpp
# 编译时启用调试
./configure --enable-debug
# 运行时增加日志
export G_MESSAGES_DEBUG=all
bluetoothd
9.2 日志级别控制
cpp
# syslog 配置
# 编辑 /etc/rsyslog.conf 或 /etc/syslog-ng/.conf
# 查看实时日志
journalctl -u bluetooth -f
journalctl -t bluetoothd -f
# 按时间过滤
journalctl --since "10 min ago" -t bluetoothd
9.3 日志过滤配置
自定义调试过滤
cpp
# 格式: -d <文件模式>[,<文件模式>...]
bluetoothd -d 'adapter.c:device.c' # 多个文件
bluetoothd -d 'adapter*.c' # glob 模式
bluetoothd -d '*' # 所有文件
控制器过滤
cpp
# btmon 过滤特定控制器
btmon -i 0 # 仅 hci0
btmon -i 1 # 仅 hci1
btmon -i 0 -i 1 # 多个控制器
优先级过滤
cpp
# btmon 按优先级过滤
btmon -p ERR # 仅显示错误
btmon -p WARN # 错误和警告
btmon -p INFO # 信息及以上
十、各业务模块日志调用
10.1 典型日志调用模式
适配器操作日志
cpp
// adapter.c - 设置设备类
DBG("Class: 0x%06x", dev_class);
// adapter.c - 发送 MGMT 命令
DBG("sending set mode command for index %u", adapter->dev_id);
设备操作日志
cpp
// device.c - 设备连接
info("Device %s connected", bdaddr_to_string(bdaddr, buf, sizeof(buf)));
// device.c - 设备断开
info("Device %s disconnected", bdaddr_to_string(bdaddr, buf, sizeof(buf)));
错误处理日志
cpp
// 错误日志(总是输出)
error("Unable to set discoverable: %s", err->message);
// 警告日志(总是输出)
warn("Agent disconnected unexpectedly");
10.2 日志调用统计
通过搜索 DBG( 可以发现,BlueZ 源码中包含大量调试日志点:
|---------|---------------|-------------|
| 模块 | 文件 | DBG 调用数 |
| 适配器管理 | adapter.c | 100+ |
| 设备管理 | device.c | 50+ |
| 代理管理 | agent.c | 20+ |
| GATT 服务 | gatt.c | 30+ |
| 广播管理 | advertising.c | 20+ |
| 广播监控 | adv_monitor.c | 25+ |
10.3 日志格式规范
BlueZ 日志遵循统一的格式规范:
cpp
[文件名]:[函数名] [消息内容]
示例:
cpp
adapter.c:set_discoverable() Changed settings: 0x00000001
adapter.c:set_discoverable() Pending settings: 0x00000000
adapter.c:start_discovery() sending MGMT_OP_START_SERVICE_DISCOVERY 0, 0, 0
十一、自定义日志重定向
11.1 重定向到文件
cpp
# 方法1: 通过 journalctl 导出
journalctl -u bluetooth --no-pager > bluetooth.log
# 方法2: 直接运行时重定向
bluetoothd -d -n 2>&1 | tee bluetooth.log
# 方法3: 使用 syslog 转发
# 配置 rsyslog 将 bluetoothd 日志发送到远程服务器
if $programname == 'bluetoothd' then @logserver:514
11.2 自定义日志过滤器
cpp
# 使用 grep 过滤
btmon | grep -E "adapter|error"
# 使用 awk 格式化
btmon | awk '{print $1, $2, $NF}'
# 按时间过滤
btmon | grep "10:30:[0-5]"
11.3 日志分析脚本
cpp
#!/bin/bash
# analyze_bt_log.sh - 蓝牙日志分析脚本
LOG_FILE=$1
echo "=== 蓝牙日志分析 ==="
# 统计错误
echo "错误统计:"
grep -c "ERR" "$LOG_FILE"
# 统计警告
echo "警告统计:"
grep -c "WARN" "$LOG_FILE"
# 常见错误分类
echo "连接错误:"
grep "connect.*fail\|connection.*error" "$LOG_FILE" | head -5
echo "认证错误:"
grep "auth.*fail\|pair.*error" "$LOG_FILE" | head -5
echo "超时错误:"
grep "timeout\|timed.out" "$LOG_FILE" | head -5
# 导出时间线
echo "关键事件时间线:"
grep -E "connect|disconnect|pair|unpair" "$LOG_FILE" | head -20
十二、调试排错实操技巧
12.1 启用详细调试
cpp
# 1. 启动 bluetoothd 并启用所有调试
sudo systemctl stop bluetooth
sudo bluetoothd -d -n 2>&1 | tee /tmp/bt_debug.log
# 2. 另一个终端启动 btmon
sudo btmon | tee /tmp/bt_monitor.log
# 3. 操作蓝牙并收集日志
bluetoothctl power on
bluetoothctl discoverable on
bluetoothctl scan on
12.2 运行时切换调试级别
cpp
# 获取 bluetoothd PID
BT_PID=$(pidof bluetoothd)
# 启用所有调试
kill -USR2 $BT_PID
# 测试蓝牙操作
bluetoothctl devices
# 查看日志变化
journalctl -u bluetooth --since "1 min ago"
12.3 内核日志配合
cpp
# 同时查看内核日志
dmesg -w | grep -i bt
# 使用 hci 工具
hciconfig hci0
hcidump -t
# 使用 btmgmt
btmgmt info
btmgmt show
12.4 常见问题排查清单
问题 1:日志无输出
排查步骤:
-
确认 syslog 服务正在运行:
systemctl status rsyslog -
检查 bluetoothd 是否以调试模式启动:
ps aux | grep bluetooth -
验证 HCI 日志通道可用:
strace -e socket bluetoothd -
检查权限:
ls -la /var/log/
问题 2:调试日志被过滤
排查步骤:
-
确认
BTD_DEBUG_FLAG_PRINT已设置 -
检查文件模式匹配:
-d '*'启用所有 -
使用
SIGUSR2强制启用 -
重新编译时启用
--enable-debug
问题 3:日志重复或丢失
排查步骤:
-
检查是否同时在 syslog 和 btmon 输出
-
确认日志缓冲区是否溢出
-
检查 HCI 日志通道连接状态
-
使用
logger命令验证 syslog
问题 4:性能影响
排查步骤:
-
减少调试模块范围:
-d 'adapter.c' -
调整 syslog 优先级过滤
-
关闭 HCI 日志通道(无 btmon 时)
-
生产环境避免使用
-d '*'
12.5 高级调试技巧
使用 GDB 调试日志
cpp
# 附加到 bluetoothd
gdb -p $(pidof bluetoothd)
# 在日志函数设置断点
break btd_debug
break btd_error
# 查看日志参数
bt
info args
p format
# 查看调试描述符
print __start___debug
print __stop___debug
使用 perf 分析日志性能
cpp
# 采集日志相关的性能数据
perf record -g -p $(pidof bluetoothd) -e syscalls:sys_enter_sendmsg
# 分析日志系统调用
perf script | grep sendmsg
十三、核心函数索引
|----------------------|------------------|----------------------|
| 函数名 | 源文件 | 功能描述 |
| info | log.c:43 | 通用信息日志(syslog + HCI) |
| btd_log | log.c:56 | 带索引的可变优先级日志 |
| btd_error | log.c:69 | 带索引的错误日志 |
| btd_warn | log.c:82 | 带索引的警告日志 |
| btd_info | log.c:95 | 带索引的信息日志 |
| btd_debug | log.c:108 | 带索引的调试日志 |
| __btd_log_init | log.c:163 | 日志系统初始化 |
| __btd_log_cleanup | log.c:182 | 日志系统清理 |
| __btd_enable_debug | log.c:141 | 批量启用调试点 |
| __btd_toggle_debug | log.c:155 | 强制启用所有调试 |
| is_enabled | log.c:126 | 检查调试点是否启用 |
| bt_log_open | shared/log.c:95 | 打开 HCI 日志通道 |
| bt_log_close | shared/log.c:172 | 关闭 HCI 日志通道 |
| bt_log_sendmsg | shared/log.c:47 | 发送日志数据包 |
| bt_log_vprintf | shared/log.c:130 | 格式化并发送日志 |
| bt_log_printf | shared/log.c:159 | varargs 版本的日志发送 |
| monitor_log | log.c:37 | HCI 日志转发入口 |
十四、日志数据流完整链路

十五、总结
BlueZ log 模块通过创新的双路日志架构(syslog + HCI Logging Channel),为蓝牙守护进程提供了强大的可观测性支持。其核心设计特点包括:
双路输出架构:同时支持本地 syslog 持久化和远程 HCI 日志传输
段属性注册:通过 ELF 段属性自动注册所有调试点,无需手动维护
运行时控制:支持 SIGUSR2 信号动态启用所有调试日志
模式匹配过滤:使用 glob 模式灵活控制各模块的日志级别
HCI 协议传输:基于标准 HCI 通道的日志传输,兼容 btmon 生态
零开销抽象:未启用的调试点完全不产生运行时开销
理解 BlueZ log 模块的实现机制,对于嵌入式蓝牙开发中的问题排查、性能优化和协议分析都具有重要的实践价值。
核心源文件路径索引
|--------------------|---------------------------------|
| 文件路径 | 关键内容 |
| src/log.h | 日志接口、btd_debug_desc 结构、DBG 宏定义 |
| src/log.c | 日志核心实现、syslog 输出、调试控制 |
| src/shared/log.h | HCI 日志通道 API 声明 |
| src/shared/log.c | HCI 日志传输实现、数据包格式定义 |
| src/main.c | 日志初始化调用、信号处理、选项解析 |
| monitor/packet.c | btmon 日志数据包解析与显示 |
| lib/hci.h | HCI 通道常量定义(HCI_CHANNEL_LOGGING) |