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_LOCAL1LOG_LOCAL6
  • 后台 LogLimit 线程做异步限流,周期性输出 [repeated N times ...] 汇总。
  • Skynet、散热控制和入侵检测的独立 syslog 通道。

1.3 核心功能

  • 模块名管理:默认 "native_code"set_log_module_name() 同时更新 syslog 设施。
  • 级别过滤:默认 DLOG_NOTICE;高于当前级别的 debug_log 直接返回。
  • 异步限流:进程加载时构造静态 Logging 对象,启动 GThread "LogLimit"
  • 专用通道:skynet_logcooling_control_logintrusion_detection_log
  • 安全格式化:vsnprintf_s / snprintf_truncated_s;过滤不可打印字符。
  • 线程安全:模块名、级别、输出类型用 shared_mutex 保护。

1.4 关键术语表

术语解释
liblogging.so实际安装的共享库文件名;Conan/CMake 目标库名是 logging
debug_log自动填入 __FILE____LINE__ 再调用同名函数。
DLOG_LEVEL_Edebug_log 使用的级别,数值与 LOG_LEVEL_E 对齐。
OUT_TYPE_E输出类型枚举。生产实现里 OUT_TYPE_FILE 走 syslog,其它值走 std::cout
g_dlog_directionsyslog 设施,默认 LOG_LOCAL1
LogLimit消费 GAsyncQueue 的后台线程名。
限流 Slot每个模块 2048 个槽,下标为 lineno % 2048
MCC_DEBUGNDEBUG 构建下设置该环境变量则绕过限流。
LOG_MAX_SZ普通日志格式化缓冲区 1024 字节。
INT_DET_MAX_SZ入侵检测日志缓冲区 8192 字节。

1.5 外部交互边界图

仓库中没有 model.jsonipmi.json。以下接口均为进程内 C API,不能通过 busctl 调用。

2. API 使用说明与示例

头文件安装路径:

类别头文件说明
公共 C APIlogging.h枚举、模块名、级别/输出类型、debug_log 宏与专用通道。
内部引擎logging_internal.hLogRecord、限流结构、internal_log_handlerlog_start
时钟logging_utils/vos.hvos_get_tick() / vos_get_tick_sec()
格式化logging_utils/format.hlogging::format_string 模板。

级别与输出类型(logging.h):

枚举取值含义
LOG_LEVEL_E / DLOG_LEVEL_EERROR WARN NOTICE INFO DEBUG MASS数值越小级别越高;level > g_dlog_level 时丢弃。
RLOG_LEVEL_E / MLOG_LEVEL_EERROR WARN INFO头文件预留,本库实现未使用。
OUT_TYPE_FILE0生产路径:syslog(g_dlog_direction | LOG_ERR, ...)
OUT_TYPE_LOCAL / OUT_TYPE_SHM1 / 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 分流和日志字段识别。

c
#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 设施
frameworkkey_mgmtpersistencedevmonLOG_LOCAL6
其它(含默认 native_codeLOG_LOCAL1

返回值与异常

C 接口不抛异常。set_log_module_namestrcpy_s 失败时打一条 DLOG_INFOset_log_module_name failed, ret=%d.)并保持原模块名。传入空指针时 strcpy_s 失败,模块名不会变成空串。

级别/类型的 setter 返回修改前的值。

应用场景

各业务进程在 main 或框架启动时设置模块名与级别;一键收集和 syslog 规则按设施与模块字段过滤。

限制条件

  • 模块名最长受 MODULE_NAME_SIZE(32,含结尾 \0)限制。
  • 路由表写死在 logging.cpp,新增 LOCAL6 模块需要改库并重新发布。
  • get_log_module_name() 返回内部静态缓冲区指针,调用方不要 free 或长时间保存后跨线程当可变字符串用。

调试示例

c
#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

c
#include "logging.h"

debug_log(DLOG_ERROR, "open shm failed, ret=%d", ret);
debug_log(DLOG_NOTICE, "service started");

生产环境默认行格式(OUT_TYPE_FILE):

text
<时间> <模块名> <级别>: <短文件名>(<行号>): <消息>[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_levelvsnprintf_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 调试
c
set_log_module_name("mctpd");
set_debug_log_level(DLOG_DEBUG);
debug_log(DLOG_DEBUG, "eid=%u binding=%s", eid, binding);
测试构建落盘

ENABLE_TESTprint_log 追加写入 /dev/shm/debug_log_for_test(权限 0666),不再走 syslog。test_package/DebugLogTest.cpp 按此路径断言。

2.3 skynet_log

功能说明

Skynet 协程框架专用接口。消息同样走限流队列,但 print_logis_skynet == 1 时固定输出到 LOG_LOCAL6 | LOG_ERR,格式为 <时间> <消息>,不含模块名、级别和文件名。lineno 字段复用为 source

c
skynet_log(source, "unhandled message from %u", from);

参数说明

参数类型说明
sourceuint32_tSkynet 服务 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 或队列未就绪时除外)。

调试示例

c
skynet_log(0x00000001, "skynet start");

2.4 cooling_control_logintrusion_detection_log

功能说明

两条通道绕过 LogLimit,直接 syslog

c
cooling_control_log("fan duty=%u", duty);
intrusion_detection_log("chassis intrusion status=%s", status);

参数说明

函数设施与优先级缓冲区前缀
cooling_control_log(fmt, ...)LOG_LOCAL7 | LOG_INFO1024微秒时间 + 空格 + 消息
intrusion_detection_log(fmt, ...)LOG_LOCAL6 | LOG_DEBUG8192(malloc同上

返回值与异常

void。vsnprintf_s 失败则返回;入侵检测在 malloc 失败时直接返回。

应用场景

散热策略与物理入侵事件需要与普通调试日志分设施,避免被 DLOG_NOTICE 阈值或限流吞掉。

限制条件

  • 不看 g_dlog_level / g_dlog_type
  • 入侵检测每条日志堆分配 8KB,高频调用会增加分配开销。
  • 无模块名、文件名、行号字段。

调试示例

c
cooling_control_log("pid output=%d", output);
intrusion_detection_log("intrusion asserted");

2.5 内部引擎与工具 API

功能说明

logging_internal.hlogging_utils 随包安装,供 libmc4lua 的 syslog 扩展等同源代码复用,不是稳定业务 API。业务组件应只依赖 logging.h

异步限流(生产、未定义 ENABLE_TEST):

常量取值含义
UNLIMIT_DURATION30 s线程启动后先 sleep,再创建队列;此前同步打印。
队列超时5 sg_async_queue_timeout_pop,空转时尝试 log_slot_flush
REC_LIMIT_DURATION300 s槽位刷新与重复汇总的时间窗。
SLOT_CNT2048每模块槽位数。
FILE_ITEM_HASH_MAX_CNT128每槽最多跟踪的「文件名+模板」数。
免费条数15每文件项在触发限流前可原样打印的条数。
ITEM_HASH_MAX_CNT15单文件项 hash 表容量;超出后归并到 hash 0。
REC_LIMIT_CNT1000重复计数达到后打印一条带 [repeated N times in Xs from ... to ...] 的日志。
ITEM_SLIST_MAX_CNT7单链超过 7 条改为 hash 表。

ENABLE_TEST 时窗口改为 5 s / 免费后 32 条 hash / 计数 500 / 启动 1 s 不限流,便于单测。

MCC_DEBUG(且非 NDEBUG)把 bypass_limit 置位,队列中的记录直接 print_log

参数说明

接口说明
internal_log_handler(rec, limit)limit == falseMCC_DEBUG 时不限流。队列为空指针则加锁同步打印。
log_start()g_thread_new("LogLimit", ...);由静态 Logging 构造函数调用。
get_log_time_str / get_log_time_str_cLOG_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_string512 字节 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 的线程。

bash
# 业务模块默认 LOCAL1
tail -f /var/log/messages | grep native_code
# 限流汇总关键字
grep -E '\[repeated|\[flush\]' /var/log/messages

3. 组件扩展案例

3.1 新 C++ 组件接入调试日志

在组件初始化中设置模块名,并在关键路径使用宏:

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

当前仅 frameworkkey_mgmtpersistencedevmon 使用 LOG_LOCAL6。若新产品要求某组件与框架日志同设施:

  1. logging.cpplist 中增加模块名字符串,且与 set_log_module_name 传入值完全一致。
  2. 重新发布 liblogger,并确认 rsyslog/journal 对 local6 的落盘规则。
  3. 不要只在业务侧改字符串而不改库:未命中列表的模块仍走 LOG_LOCAL1

3.3 临时打开 DEBUG 与关闭限流

排障时在非 Release/NDEBUG 镜像上:

bash
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_logOUT_TYPE_FILE)。
syslog LOG_LOCAL6framework / key_mgmt / persistence / devmondebug_log;全部 skynet_logintrusion_detection_log
syslog LOG_LOCAL7cooling_control_log
标准输出g_dlog_type != OUT_TYPE_FILE 时的 debug_log / 限流汇总。
/dev/shm/debug_log_for_testENABLE_TEST 构建。

具体落盘文件取决于镜像中的 rsyslog/syslog-ng 配置,不在本仓库声明。

4.2 关键日志信息

日志片段级别含义解读建议处理动作
set_log_module_name failed, ret=INFOstrcpy_s 失败(空指针或超长)。检查传入模块名。
[repeated N times in Xs from ... to ...]与原日志相同限流汇总,相同日志被合并。查对应源文件行号;高频循环应降级或去重。
[flush]同上槽位超时刷新时打出的汇总。确认 REC_LIMIT_DURATION 窗口内的重复次数。
[period:N(s)]同上带周期的日志(Lua/syslog 扩展可设 period)。debug_log 本身不填 period
WARNINGWARN级别字符串映射,不是拼写错误。按 WARN 过滤时用 WARNING

5. 问题定界指南

5.1 典型问题定界

问题描述是否为本组件问题判断依据关键证据收集方法
链接不到 liblogging.so / logging可能是包名是 liblogger,文件名是 liblogging.soldd;确认 usr/lib64/liblogging.so
busctl 找不到 liblogger不是本组件不是 D-Bus 服务。改查宿主进程 syslog。
Lua require 'liblogger' 失败不是无 Lua 模块;应 require 'mc.logging'见 libmc4lua 文档。
DEBUG 日志完全没有可能是级别或 syslog默认阈值 DLOG_NOTICEget_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_sEOK模块名设置失败避免 NULL,缩短名称。
vsnprintf_s < 0格式化失败,本条丢弃检查格式串与参数匹配。
malloc 失败入侵检测日志丢弃查内存;不要对入侵路径打超长变参。
vos_get_tick() == 0时钟读取失败ARM 上检查 /dev/sys_info

没有 D-Bus 结构化错误对象。

5.3 调试方法

开启调试日志

  1. set_debug_log_level(DLOG_DEBUG) 或框架 MicroComponent.Debug.SetDlogLevel
  2. 确认 get_debug_log_type()OUT_TYPE_FILE(syslog)或按需改为 stdout。
  3. 非生产构建可设 MCC_DEBUG 关闭限流。

复现问题方法

  1. 确认进程已链接 usr/lib64/liblogging.so,存在 LogLimit 线程。
  2. test_packageDebugLogTestENABLE_TEST)验证写 /dev/shm/debug_log_for_test
  3. 对照 syslog 设施:普通模块 local1,名单模块与 Skynet/入侵 local6,散热 local7
  4. 不要对 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,否则 coutOUT_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。