liblogger
版本信息
| 项目 | 内容 |
|---|---|
| 组件版本 | 1.110.7 |
| 首发版本 | openUBMC 26.09 |
| 文档作者 | openUBMC 社区 |
| 最后更新 | 2026-09-17 |
| 许可证 | Mulan PSL v2 |
1. 组件概述
1.1 组件简介
liblogger 是 openUBMC 框架的 C/C++ 日志库。
组件不启动独立服务进程、不注册 D-Bus 资源协作接口,也不提供 IPMI 命令或 Lua require 入口。公开能力是进程内 C API:业务代码 #include "logging.h" 并链接 logging,通过 debug_log 宏、Skynet 专用接口以及散热/入侵检测专用通道写日志。
Lua 业务进程应使用 libmc4lua 的 mc.logging / syslog.core(其 C 扩展 #include "logging.h"),不要把 liblogger 当成 Lua 模块加载。
1.2 解决什么问题
BMC 上大量微服务需要统一的级别过滤、syslog 设施路由和高频日志限流。如果每个组件各自 printf/syslog,会出现级别不一致、重复日志淹没磁盘、以及模块无法按设施分流。liblogger 提供:
- 六级调试日志(ERROR~MASS)与进程内级别阈值。
- 按模块名把日志路由到
LOG_LOCAL1或LOG_LOCAL6。 - 后台
LogLimit线程做异步限流,周期性输出[repeated N times ...]汇总。 - Skynet、散热控制和入侵检测的独立 syslog 通道。
1.3 核心功能
- 模块名管理:默认
"native_code";set_log_module_name()同时更新 syslog 设施。 - 级别过滤:默认
DLOG_NOTICE;高于当前级别的debug_log直接返回。 - 异步限流:进程加载时构造静态
Logging对象,启动 GThread"LogLimit"。 - 专用通道:
skynet_log、cooling_control_log、intrusion_detection_log。 - 安全格式化:
vsnprintf_s/snprintf_truncated_s;过滤不可打印字符。 - 线程安全:模块名、级别、输出类型用
shared_mutex保护。
1.4 关键术语表
| 术语 | 解释 |
|---|---|
liblogging.so | 实际安装的共享库文件名;Conan/CMake 目标库名是 logging。 |
debug_log 宏 | 自动填入 __FILE__、__LINE__ 再调用同名函数。 |
DLOG_LEVEL_E | debug_log 使用的级别,数值与 LOG_LEVEL_E 对齐。 |
OUT_TYPE_E | 输出类型枚举。生产实现里 OUT_TYPE_FILE 走 syslog,其它值走 std::cout。 |
g_dlog_direction | syslog 设施,默认 LOG_LOCAL1。 |
LogLimit | 消费 GAsyncQueue 的后台线程名。 |
| 限流 Slot | 每个模块 2048 个槽,下标为 lineno % 2048。 |
MCC_DEBUG | 非 NDEBUG 构建下设置该环境变量则绕过限流。 |
LOG_MAX_SZ | 普通日志格式化缓冲区 1024 字节。 |
INT_DET_MAX_SZ | 入侵检测日志缓冲区 8192 字节。 |
1.5 外部交互边界图
仓库中没有 model.json 或 ipmi.json。以下接口均为进程内 C API,不能通过 busctl 调用。
2. API 使用说明与示例
头文件安装路径:
| 类别 | 头文件 | 说明 |
|---|---|---|
| 公共 C API | logging.h | 枚举、模块名、级别/输出类型、debug_log 宏与专用通道。 |
| 内部引擎 | logging_internal.h | LogRecord、限流结构、internal_log_handler、log_start。 |
| 时钟 | logging_utils/vos.h | vos_get_tick() / vos_get_tick_sec()。 |
| 格式化 | logging_utils/format.h | logging::format_string 模板。 |
级别与输出类型(logging.h):
| 枚举 | 取值 | 含义 |
|---|---|---|
LOG_LEVEL_E / DLOG_LEVEL_E | ERROR WARN NOTICE INFO DEBUG MASS | 数值越小级别越高;level > g_dlog_level 时丢弃。 |
RLOG_LEVEL_E / MLOG_LEVEL_E | ERROR WARN INFO | 头文件预留,本库实现未使用。 |
OUT_TYPE_FILE | 0 | 生产路径:syslog(g_dlog_direction | LOG_ERR, ...)。 |
OUT_TYPE_LOCAL / OUT_TYPE_SHM | 1 / 2 | 枚举存在;print_log 将非 FILE 一律写到 std::cout。 |
OUT_TYPE_BUTT | 哨兵 | 非法类型边界。 |
get_log_level_str() 将级别格式化为 ERROR / WARNING / NOTICE / INFO / DEBUG / MASS(WARN 输出字符串是 WARNING)。
2.1 模块名、级别与输出类型
功能说明
进程加载库时静态对象把模块名设为 "native_code"、级别设为 DLOG_NOTICE、输出类型设为 OUT_TYPE_FILE。组件初始化时应设置模块名,以便 syslog 分流和日志字段识别。
#include "logging.h"
set_log_module_name("hwproxy");
DLOG_LEVEL_E old = set_debug_log_level(DLOG_INFO);
(void)old;参数说明
| 函数 | 参数 | 返回值 | 说明 |
|---|---|---|---|
get_log_module_name() | 无 | const char* | 当前模块名;默认 "native_code"。 |
set_log_module_name(module) | C 字符串 | void | 拷入 32 字节缓冲区 gModuleName。 |
get_debug_log_type() | 无 | OUT_TYPE_E | 当前输出类型。 |
set_debug_log_type(type) | OUT_TYPE_E | 旧类型 | 更新 g_dlog_type。 |
get_debug_log_level() | 无 | DLOG_LEVEL_E | 当前级别阈值。 |
set_debug_log_level(level) | DLOG_LEVEL_E | 旧级别 | 更新 g_dlog_level。 |
set_log_module_name 的 syslog 路由:
| 模块名 | syslog 设施 |
|---|---|
framework、key_mgmt、persistence、devmon | LOG_LOCAL6 |
其它(含默认 native_code) | LOG_LOCAL1 |
返回值与异常
C 接口不抛异常。set_log_module_name 在 strcpy_s 失败时打一条 DLOG_INFO(set_log_module_name failed, ret=%d.)并保持原模块名。传入空指针时 strcpy_s 失败,模块名不会变成空串。
级别/类型的 setter 返回修改前的值。
应用场景
各业务进程在 main 或框架启动时设置模块名与级别;一键收集和 syslog 规则按设施与模块字段过滤。
限制条件
- 模块名最长受
MODULE_NAME_SIZE(32,含结尾\0)限制。 - 路由表写死在
logging.cpp,新增 LOCAL6 模块需要改库并重新发布。 get_log_module_name()返回内部静态缓冲区指针,调用方不要free或长时间保存后跨线程当可变字符串用。
调试示例
#include "logging.h"
#include <stdio.h>
int main(void)
{
printf("module=%s level=%d type=%d\n",
get_log_module_name(),
(int)get_debug_log_level(),
(int)get_debug_log_type());
set_log_module_name("persistence");
set_debug_log_level(DLOG_DEBUG);
return 0;
}2.2 debug_log 调试日志
功能说明
推荐使用宏,不要直接调函数(宏会覆盖函数名)。宏把源文件和行号传给实现,实现按级别过滤、格式化、过滤非法字符,再交给 internal_log_handler。
#include "logging.h"
debug_log(DLOG_ERROR, "open shm failed, ret=%d", ret);
debug_log(DLOG_NOTICE, "service started");生产环境默认行格式(OUT_TYPE_FILE):
<时间> <模块名> <级别>: <短文件名>(<行号>): <消息>[repeated ... 可选]时间由 strftime("%F %T") 加微秒,形如 2023-09-11 10:02:11.123456。文件名只保留最后一段路径。
参数说明
| 接口 | 参数 | 说明 |
|---|---|---|
debug_log(level, fmt, ...) 宏 | 级别、printf 风格格式串与变参 | 展开为 debug_log(level, __FILE__, __LINE__, fmt, ##arg)。 |
函数 debug_log(level, file, line, fmt, ...) | 另含文件名与行号 | 仅实现文件在 #undef debug_log 后调用。 |
返回值与异常
void。level > g_dlog_level 或 vsnprintf_s 失败时静默返回。消息先经 filter_invalid_chars:可打印 ASCII(32–126)保留;\t\b\f\r\n\v\x7F 等替换为空格;并去掉首尾空格。
应用场景
C/C++ 组件的常规诊断日志。Lua 侧等价能力见 mc.logging,不要在 Lua 里直接 require 本库。
限制条件
- 单条格式化结果截断在 1023 字符(
LOG_MAX_SZ)。 - 行号在
LogRecord中占 16 bit,并参与lineno % 2048分槽。 - 队列未创建前(启动后约
UNLIMIT_DURATION秒内)日志同步打印,不限流。 NDEBUG构建下MCC_DEBUG无效。
调试示例
C 调试
set_log_module_name("mctpd");
set_debug_log_level(DLOG_DEBUG);
debug_log(DLOG_DEBUG, "eid=%u binding=%s", eid, binding);测试构建落盘
ENABLE_TEST 时 print_log 追加写入 /dev/shm/debug_log_for_test(权限 0666),不再走 syslog。test_package/DebugLogTest.cpp 按此路径断言。
2.3 skynet_log
功能说明
Skynet 协程框架专用接口。消息同样走限流队列,但 print_log 在 is_skynet == 1 时固定输出到 LOG_LOCAL6 | LOG_ERR,格式为 <时间> <消息>,不含模块名、级别和文件名。lineno 字段复用为 source。
skynet_log(source, "unhandled message from %u", from);参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
source | uint32_t | Skynet 服务 handle,写入记录的行号字段。 |
fmt, ... | printf 风格 | 与 debug_log 相同的 1024 字节缓冲与非法字符过滤。 |
不受 g_dlog_level 过滤;记录内 level 被设为 LOG_ERR(syslog 优先级常量,不是 DLOG_* 枚举)。
返回值与异常
void。格式化失败则返回。
应用场景
Skynet 运行时把脚本/框架日志导入 BMC syslog。Lua 业务日志优先走 mc.logging。
限制条件
始终 LOG_LOCAL6,不使用 g_dlog_direction。仍走 LogLimit 队列,可被限流(MCC_DEBUG 或队列未就绪时除外)。
调试示例
skynet_log(0x00000001, "skynet start");2.4 cooling_control_log 与 intrusion_detection_log
功能说明
两条通道绕过 LogLimit,直接 syslog。
cooling_control_log("fan duty=%u", duty);
intrusion_detection_log("chassis intrusion status=%s", status);参数说明
| 函数 | 设施与优先级 | 缓冲区 | 前缀 |
|---|---|---|---|
cooling_control_log(fmt, ...) | LOG_LOCAL7 | LOG_INFO | 1024 | 微秒时间 + 空格 + 消息 |
intrusion_detection_log(fmt, ...) | LOG_LOCAL6 | LOG_DEBUG | 8192(malloc) | 同上 |
返回值与异常
void。vsnprintf_s 失败则返回;入侵检测在 malloc 失败时直接返回。
应用场景
散热策略与物理入侵事件需要与普通调试日志分设施,避免被 DLOG_NOTICE 阈值或限流吞掉。
限制条件
- 不看
g_dlog_level/g_dlog_type。 - 入侵检测每条日志堆分配 8KB,高频调用会增加分配开销。
- 无模块名、文件名、行号字段。
调试示例
cooling_control_log("pid output=%d", output);
intrusion_detection_log("intrusion asserted");2.5 内部引擎与工具 API
功能说明
logging_internal.h 与 logging_utils 随包安装,供 libmc4lua 的 syslog 扩展等同源代码复用,不是稳定业务 API。业务组件应只依赖 logging.h。
异步限流(生产、未定义 ENABLE_TEST):
| 常量 | 取值 | 含义 |
|---|---|---|
UNLIMIT_DURATION | 30 s | 线程启动后先 sleep,再创建队列;此前同步打印。 |
| 队列超时 | 5 s | g_async_queue_timeout_pop,空转时尝试 log_slot_flush。 |
REC_LIMIT_DURATION | 300 s | 槽位刷新与重复汇总的时间窗。 |
SLOT_CNT | 2048 | 每模块槽位数。 |
FILE_ITEM_HASH_MAX_CNT | 128 | 每槽最多跟踪的「文件名+模板」数。 |
| 免费条数 | 15 | 每文件项在触发限流前可原样打印的条数。 |
ITEM_HASH_MAX_CNT | 15 | 单文件项 hash 表容量;超出后归并到 hash 0。 |
REC_LIMIT_CNT | 1000 | 重复计数达到后打印一条带 [repeated N times in Xs from ... to ...] 的日志。 |
ITEM_SLIST_MAX_CNT | 7 | 单链超过 7 条改为 hash 表。 |
ENABLE_TEST 时窗口改为 5 s / 免费后 32 条 hash / 计数 500 / 启动 1 s 不限流,便于单测。
MCC_DEBUG(且非 NDEBUG)把 bypass_limit 置位,队列中的记录直接 print_log。
参数说明
| 接口 | 说明 |
|---|---|
internal_log_handler(rec, limit) | limit == false 或 MCC_DEBUG 时不限流。队列为空指针则加锁同步打印。 |
log_start() | g_thread_new("LogLimit", ...);由静态 Logging 构造函数调用。 |
get_log_time_str / get_log_time_str_c | LOG_TIME 秒级、LOG_US_TIME 微秒;C 版使用 thread_local 64 字节缓冲。 |
filter_invalid_chars | 见 2.2。 |
vos_get_tick() | 毫秒滴答。ARM(非 32 位)走 /dev/sys_info ioctl SYS_INFO_GET_JIFFIES 再 ×10;其它平台 gettimeofday。 |
vos_get_tick_sec() | vos_get_tick() / 1000。 |
format_string | 512 字节 snprintf_truncated_s,返回 std::string。 |
返回值与异常
内部接口无统一错误码。vos_get_tick 在 /dev/sys_info 打开或 ioctl 失败时返回 0。
应用场景
libmc4lua l_syslog.cpp 组装 LogRecord 后调用 internal_log_handler,以复用限流与 print_log。
限制条件
不要在业务仓直接依赖 LogRecord 布局或 Slot 实现,升级 liblogger 可能改变内部结构。
调试示例
内部接口没有独立的 busctl/mdbctl 入口。确认限流是否生效:看日志是否出现 [repeated / [flush],以及进程中是否存在名为 LogLimit 的线程。
# 业务模块默认 LOCAL1
tail -f /var/log/messages | grep native_code
# 限流汇总关键字
grep -E '\[repeated|\[flush\]' /var/log/messages3. 组件扩展案例
3.1 新 C++ 组件接入调试日志
在组件初始化中设置模块名,并在关键路径使用宏:
#include "logging.h"
void app_init(void)
{
set_log_module_name("my_app");
set_debug_log_level(DLOG_NOTICE);
debug_log(DLOG_NOTICE, "my_app started");
}CMake/Conan 依赖 liblogger,链接库名 logging(产物 liblogging.so)。Lua 组件不要复制此模式,应 require 'mc.logging'。
3.2 把新模块打到 LOCAL6
当前仅 framework、key_mgmt、persistence、devmon 使用 LOG_LOCAL6。若新产品要求某组件与框架日志同设施:
- 在
logging.cpp的list中增加模块名字符串,且与set_log_module_name传入值完全一致。 - 重新发布 liblogger,并确认 rsyslog/journal 对
local6的落盘规则。 - 不要只在业务侧改字符串而不改库:未命中列表的模块仍走
LOG_LOCAL1。
3.3 临时打开 DEBUG 与关闭限流
排障时在非 Release/NDEBUG 镜像上:
export MCC_DEBUG=1
# 并在进程内 set_debug_log_level(DLOG_DEBUG) 或通过 MicroComponent.Debug.SetDlogLevel生产 NDEBUG 构建中 is_debug() 恒为 false,仅靠级别阈值过滤,限流仍生效。
4. 日志说明
4.1 一键日志收集
liblogger 是日志后端本身,不创建组件私有日志文件,也没有 on_dump。一键收集拿到的是宿主进程经 syslog 落下的文本。
| 路径 / 设施 | 内容说明 |
|---|---|
syslog LOG_LOCAL1 | 默认及未列入 LOCAL6 名单的模块的 debug_log(OUT_TYPE_FILE)。 |
syslog LOG_LOCAL6 | framework / key_mgmt / persistence / devmon 的 debug_log;全部 skynet_log;intrusion_detection_log。 |
syslog LOG_LOCAL7 | cooling_control_log。 |
| 标准输出 | g_dlog_type != OUT_TYPE_FILE 时的 debug_log / 限流汇总。 |
/dev/shm/debug_log_for_test | 仅 ENABLE_TEST 构建。 |
具体落盘文件取决于镜像中的 rsyslog/syslog-ng 配置,不在本仓库声明。
4.2 关键日志信息
| 日志片段 | 级别 | 含义解读 | 建议处理动作 |
|---|---|---|---|
set_log_module_name failed, ret= | INFO | strcpy_s 失败(空指针或超长)。 | 检查传入模块名。 |
[repeated N times in Xs from ... to ...] | 与原日志相同 | 限流汇总,相同日志被合并。 | 查对应源文件行号;高频循环应降级或去重。 |
[flush] | 同上 | 槽位超时刷新时打出的汇总。 | 确认 REC_LIMIT_DURATION 窗口内的重复次数。 |
[period:N(s)] | 同上 | 带周期的日志(Lua/syslog 扩展可设 period)。 | debug_log 本身不填 period。 |
WARNING | WARN | 级别字符串映射,不是拼写错误。 | 按 WARN 过滤时用 WARNING。 |
5. 问题定界指南
5.1 典型问题定界
| 问题描述 | 是否为本组件问题 | 判断依据 | 关键证据收集方法 |
|---|---|---|---|
链接不到 liblogging.so / logging | 可能是 | 包名是 liblogger,文件名是 liblogging.so。 | ldd;确认 usr/lib64/liblogging.so。 |
busctl 找不到 liblogger | 不是 | 本组件不是 D-Bus 服务。 | 改查宿主进程 syslog。 |
Lua require 'liblogger' 失败 | 不是 | 无 Lua 模块;应 require 'mc.logging'。 | 见 libmc4lua 文档。 |
| DEBUG 日志完全没有 | 可能是级别或 syslog | 默认阈值 DLOG_NOTICE。 | get_debug_log_level;查 rsyslog 对 local1/local6 的过滤。 |
| 日志在 stdout 而不是 syslog | 可能是 | OUT_TYPE_FILE 才走 syslog。 | get_debug_log_type()。 |
| 模块未进 LOCAL6 | 可能是 | 仅四个硬编码模块名走 LOCAL6。 | 对照 set_log_module_name 实参。 |
| 高频日志被合并 | 是预期 | 免费 15 条后进入重复计数。 | 搜 [repeated;非 NDEBUG 下试 MCC_DEBUG。 |
| ARM 上限流时间异常 | 可能是 | vos_get_tick 依赖 /dev/sys_info。 | 检查设备节点与 ioctl。 |
5.2 错误码速查表
| 错误码 / 现象 | 含义 | 排查建议 |
|---|---|---|
| 无返回值(void) | 公共日志 API 不返回错误码 | 用级别、设施和文本内容判断。 |
strcpy_s 非 EOK | 模块名设置失败 | 避免 NULL,缩短名称。 |
vsnprintf_s < 0 | 格式化失败,本条丢弃 | 检查格式串与参数匹配。 |
malloc 失败 | 入侵检测日志丢弃 | 查内存;不要对入侵路径打超长变参。 |
vos_get_tick() == 0 | 时钟读取失败 | ARM 上检查 /dev/sys_info。 |
没有 D-Bus 结构化错误对象。
5.3 调试方法
开启调试日志
set_debug_log_level(DLOG_DEBUG)或框架MicroComponent.Debug.SetDlogLevel。- 确认
get_debug_log_type()为OUT_TYPE_FILE(syslog)或按需改为 stdout。 - 非生产构建可设
MCC_DEBUG关闭限流。
复现问题方法
- 确认进程已链接
usr/lib64/liblogging.so,存在LogLimit线程。 - 用
test_package的DebugLogTest(ENABLE_TEST)验证写/dev/shm/debug_log_for_test。 - 对照 syslog 设施:普通模块
local1,名单模块与 Skynet/入侵local6,散热local7。 - 不要对 liblogger 使用
busctl。
5.4 错误对象解读
本组件不返回 D-Bus 错误对象。失败表现为日志缺失、静默丢弃或 syslog 设施不符合预期。Lua 调用链上的异常属于 libmc4lua,不在本库抛出。
6. 常见问题解答
Q1:为什么找不到 liblogger.so?
- 问题描述:按组件名去链
liblogger失败。 - 一句话答案:安装文件名是
liblogging.so,链接名是logging。 - 根因说明:CMake
add_library(logging SHARED ...)且install(... DESTINATION usr/lib64)。 - 解决方案:Conan 依赖
liblogger,链接logging;运行时提供usr/lib64/liblogging.so。 - 规避方案:不要按仓库目录名猜测
.so文件名。 - 适用版本:1.110.7。
Q2:为什么默认看不到 DEBUG 日志?
- 问题描述:调用了
debug_log(DLOG_DEBUG, ...)但 syslog 没有对应行。 - 一句话答案:默认阈值是
DLOG_NOTICE,更低级别会被直接丢掉。 - 根因说明:
debug_log函数开头if (level > g_dlog_level) return;。 - 解决方案:
set_debug_log_level(DLOG_DEBUG)或框架 Debug 接口;并确认 rsyslog 未丢弃该设施。 - 规避方案:生产路径只用 NOTICE 及以上。
- 适用版本:1.110.7。
Q3:OUT_TYPE_FILE 为什么不是写文件?
- 问题描述:按枚举名以为会写本地文件。
- 一句话答案:生产实现里
OUT_TYPE_FILE表示走 syslog;测试构建才写/dev/shm/debug_log_for_test。 - 根因说明:
print_log在非ENABLE_TEST时,g_dlog_type == OUT_TYPE_FILE调用syslog,否则cout。OUT_TYPE_LOCAL/OUT_TYPE_SHM没有独立实现分支。 - 解决方案:按 syslog 设施收集;需要 stdout 时改
set_debug_log_type为非 FILE。 - 规避方案:不要把枚举名理解成文件系统路径。
- 适用版本:1.110.7。
Q4:高频日志只看到 [repeated N times ...] 怎么办?
- 问题描述:循环里打的日志被合并。
- 一句话答案:这是限流引擎的预期行为,不是丢失。
- 根因说明:每槽每文件免费 15 条后按内容 hash 计数,达到 1000 或超过 300 s 窗口再汇总打印。
- 解决方案:降低频率、提高级别区分、或在非 NDEBUG 镜像设
MCC_DEBUG。 - 规避方案:不要在成功路径的紧凑循环里打 INFO/NOTICE。
- 适用版本:1.110.7。
Q5:Lua 里能不能直接用 liblogger?
- 问题描述:
require失败,或希望在 Lua 调debug_log。 - 一句话答案:不能把 liblogger 当 Lua 模块;使用
require 'mc.logging'。 - 根因说明:本库无
luaopen_*。libmc4lua 的syslog.core链接logging.h后再封装为mc.logging。 - 解决方案:见 libmc4lua 文档中的
mc.logging章节。 - 规避方案:C/C++ 组件继续
#include "logging.h"。 - 适用版本:1.110.7。