Skip to content

本页详细剖析 Tracer 静态日志类——OTX 代理插件中唯一的日志基础设施。它定义了六级日志阈值体系(Off → Debug),以全静态方法设计提供零实例化的调用体验,并通过统一的 saveLogger 管道将格式化日志输出到控制台或文件。同时,它通过 otxproxy.h 中的 C 语言 API 向 OTX 运行时暴露日志能力,使得上层诊断脚本也能通过 loggerlogger_hexsetLoggerLevel 等函数进行日志记录与级别控制。

分级阈值体系:Trace_Level_t 枚举

日志系统的核心是一个六级枚举 Trace_Level_t,从完全静默到最详细调试输出逐级递增。每一级对应的宏数值与语义如下表:

级别常量数值语义触发条件
Trace_Level_Off0关闭所有日志输出当前级别设为 0 时,所有日志方法静默跳过
Trace_Level_Logger1仅输出 logger 级别(最低基础输出)traceLevel >= 1logger() 生效
Trace_Level_Error2输出 Error 及以上traceLevel >= 2error() 生效(包含 logger)
Trace_Level_Warn3输出 Warn 及以上traceLevel >= 3warn() 生效(包含 error + logger)
Trace_Level_Info4输出 Info 及以上traceLevel >= 4info() 生效
Trace_Level_Debug5输出 Debug 及以上(全部)traceLevel >= 5debug()hex() 生效

默认级别为 Trace_Level_Debug(5),即开发阶段默认输出所有日志。级别门控模式在每个公开方法中实现,例如 error() 仅在 traceLevel >= Trace_Level_Error 时执行输出——这是一种典型的阈值过滤器模式,以极低的运行时开销(单次整数比较)实现级别控制。级别切换通过 setTraceLevel(int v) 完成,该方法会进行边界校验(0~5),非法值返回 false 并保持原级别不变。

Sources: tracer.h, tracer.cpp, tracer.cpp

类架构:全静态设计

Tracer 类采用纯静态设计——所有公开方法和私有成员均为 static,不需要实例化即可使用。这种设计使日志调用在代码中极其简洁:Tracer::info("tag", "message") 一行即可完成记录。

mermaid
classDiagram
    class Tracer {
        <<static class>>
        -static Trace_Level_t traceLevel
        +static setConsoleColor(int color) void
        +static json(string tag, cJSON* root) bool
        +static error(string tag, string msg) void
        +static warn(string tag, string msg) void
        +static info(string tag, string msg) void
        +static debug(string tag, string msg) void
        +static logger(string tag, string msg, string level) void
        +static hex(string tag, unsigned char* msg, int len) void
        +static getTraceLevel() int
        +static setTraceLevel(int v) int
        -static currentTimestamp() string
        -static saveLogger(string levelString, string tag, string msg) void
        -static append2Console(string levelString, string tag, string msg) void
        -static append2File(string levelString, string tag, string msg) void
    }
    Tracer ..> Dateformat : uses currentTimeString()
    Tracer ..> MyStringUtils : uses UTF8ToGBK()

类的内部依赖关系十分简洁:currentTimestamp() 委托给 Dateformat::currentTimeString() 获取格式化时间戳,saveLogger() 在输出到控制台时通过 MyStringUtils::UTF8ToGBK() 将 UTF-8 编码的 tag 和 msg 转换为 GBK 编码以正确显示在 Windows 控制台上。

Sources: tracer.h, tracer.cpp

六级输出方法详解

logger():基础输出与参数交换机制

logger() 是所有日志输出的最终公共路径——json()logger_num() 最终都委托给它。它接受三个参数,其中 level 有默认值 "[logger]"

cpp
static void logger(string tag, string msg, string level="[logger]");

该方法内含一个参数交换逻辑:当 level == "[logger]" 时,tag 和 msg 保持原样,空值被替换为 "null";当 level 不为 "[logger]" 时(通常由 json() 触发,此时 cJSON 序列化结果作为 level 传入),tag 和 msg 的位置会被交换——原 level 成为输出内容,原 tag 成为标签。这种设计允许 cJSON 的序列化字符串直接作为日志主体输出。

Sources: tracer.h, tracer.cpp

error():红色高亮的错误输出

error() 在 Windows 平台(_MSC_VER)上通过 Win32 Console API 设置红色前景色(FOREGROUND_RED),输出完成后恢复默认白色(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)。此外,它对消息长度做了 4096 字节的截断保护——超过 4096 字节的消息将被静默丢弃。这在生产环境中防止了异常巨大的日志消息导致控制台 I/O 阻塞。

Sources: tracer.cpp

warn()、info()、debug():标准分级输出

三者结构完全一致,仅在阈值检查级别和前缀标签上有所区别:

方法阈值条件前缀标签
warn()traceLevel >= Trace_Level_Warn (3)[warn]>>
info()traceLevel >= Trace_Level_Info (4)[info]>>
debug()traceLevel >= Trace_Level_Debug (5)[debug]>>

这种高度一致的实现模式体现了模板方法模式的简化应用——相同流程(阈值检查 → 调用 saveLogger),仅参数(阈值常量、前缀字符串)不同。

Sources: tracer.cpp

hex():带隐私遮蔽的十六进制输出

hex() 用于输出二进制数据的十六进制表示,级别阈值为 Trace_Level_Debug。它包含两个关键设计:

  1. 长度限制:超过 4096 字节的数据将被丢弃,与 error() 的截断保护一致。
  2. 隐私遮蔽:当数据长度大于 6 字节时,前 4 字节和后 3 字节正常显示为十六进制(如 A1B2C3D4),中间部分全部替换为 **。这在诊断场景中保护了敏感数据(如 VIN 码、密钥材料),同时保留了首尾字节供调试定位。
cpp
// hex 输出的遮蔽逻辑示意
// 输入: [01 02 03 04 | 05 06 07 08 09 0A | 0B 0C 0D]
// 输出: "01020304****************0B0C0D"

Sources: tracer.cpp

json():cJSON 结构体的序列化输出

json() 接收一个 cJSON* 指针,内部调用 cJSON_Print() 序列化为字符串后委托给 logger()。与直接调用 logger() 不同的是,json() 传入的序列化字符串会作为 level 参数而非 msg,从而触发 logger() 的参数交换逻辑,使 JSON 内容成为主体输出。

Sources: tracer.cpp

输出管道:saveLogger → append2Console / append2File

所有日志方法的输出最终汇聚到私有方法 saveLogger(),它是整个日志系统的流控制枢纽

mermaid
flowchart LR
    A[error] --> G[saveLogger]
    B[warn] --> G
    C[info] --> G
    D[debug] --> G
    E[logger] --> G
    F[hex] --> G
    G -->|#if true| H[append2Console]
    G -->|#if false| I[append2File]
    H --> J[cout + fflush]
    I --> K[./trace.txt]

当前编译配置(#if true)将输出路由到 append2Console(),它通过 cout 输出,格式为 <levelString> <tag> <msg>(当前版本不附带时间戳——代码中注释说明"打印统一由 APP 打印")。每次输出后调用 fflush(stdout) 确保立即刷新,这在崩溃诊断场景中至关重要。

备选路径 append2File() 则追加写入 ./trace.txt 文件,格式为 <timestamp><levelString><tag> : <msg>——与 console 版本不同,文件版本包含时间戳前缀,便于离线分析。

在 Windows 平台,append2Console() 会通过 MyStringUtils::UTF8ToGBK() 将 UTF-8 字符串转换为 GBK 编码后再输出,以解决 Windows 控制台代码页(默认 GBK/CP936)与源代码 UTF-8 编码不匹配导致的乱码问题。

Sources: tracer.cpp

C 语言 API 封装:向 OTX 运行时暴露日志能力

otxproxy.h 中声明了四个 OTX_API 导出函数,作为 Tracer 类与 OTX 运行时之间的适配层。这些函数以 C 语言链接约定(extern "C")暴露,使 OTX 诊断脚本可以跨语言边界调用日志功能:

C API 函数签名功能
loggervoid logger(char* tag, char* msg)记录日志,自动识别 bracket 标签
logger_numvoid logger_num(char* tag, int number)记录数值型日志
logger_hexvoid logger_hex(char* tag, unsigned char* u8Buffer, int len)记录十六进制数据(含遮蔽)
setLoggerLevelvoid setLoggerLevel(int level)设置日志级别(0~5)

logger() 的 C 封装中包含一项特殊逻辑:如果 tag[ 开头并以 ] 结尾(如 [custom_level]),则该 tag 会被提升为日志级别标签——调用 Tracer::logger("", msg, tag),将方括号内容作为自定义 level 前缀。这允许 OTX 脚本自定义日志级别前缀,而不必局限于预定义的 error/warn/info/debug 标签。

同时,otxproxy.cpp 在静态初始化阶段(loadFirst() 函数,通过静态变量 version 触发)记录编译时间戳:

cpp
Tracer::logger("@compiled", tempBuffer);  // tempBuffer = "__DATE__ __TIME__"

这确保了插件加载时自动输出编译日期与时间,便于运维人员确认部署版本。

Sources: otxproxy.h, otxproxy.cpp, otxproxy.cpp

线程安全考量

Tracer 类本身不提供内部互斥锁——所有静态方法直接操作共享状态(traceLevel 的读写)和共享资源(cout 和文件流)。这并非疏忽,而是一种刻意的设计权衡:日志系统追求最小延迟和最低开销,加锁会引入竞争和阻塞。在多线程环境中(如 OTX 代理的刷写引擎和 HTTP 通信模块),调用者应自行确保日志调用的线程安全。otxproxy.cpp 中的 logger C 函数也未加锁,意味着当多个 OTX 脚本线程同时调用 logger() 时,控制台输出可能交错。

Sources: tracer.h, tracer.cpp

在项目中的使用模式

Tracer 被项目内多个模块通过 #include "tracer.h" 引入。典型的使用模式如下:

  • JSON 解析错误Tracer::error("couldn't parsed to json", jsonString) —— 在 jsonNum() 中记录无法解析的 JSON 字符串
  • 参数校验失败Tracer::error("无效的参数", "key") —— 在 jsonNum() 中记录空 key 参数
  • 类型错误Tracer::error("json type", "not number") —— 在 jsonNum() 中记录类型不匹配
  • 编译信息Tracer::logger("@compiled", tempBuffer) —— 在 loadFirst() 中记录编译时间
  • 调试信息Tracer::info("raw", rawString) —— 在 addJsonKey() 中记录原始输入

所有这些调用都遵循 Tracer::<level>(tag, message) 的统一范式,tag 用于标识模块或上下文,message 提供具体信息。

Sources: otxproxy.cpp, otxproxy.cpp, otxproxy.cpp, otxproxy.cpp

阅读指引

理解 Tracer 之后,建议继续了解以下关联模块: