本页详细剖析 Tracer 静态日志类——OTX 代理插件中唯一的日志基础设施。它定义了六级日志阈值体系(Off → Debug),以全静态方法设计提供零实例化的调用体验,并通过统一的 saveLogger 管道将格式化日志输出到控制台或文件。同时,它通过 otxproxy.h 中的 C 语言 API 向 OTX 运行时暴露日志能力,使得上层诊断脚本也能通过 logger、logger_hex、setLoggerLevel 等函数进行日志记录与级别控制。
分级阈值体系:Trace_Level_t 枚举
日志系统的核心是一个六级枚举 Trace_Level_t,从完全静默到最详细调试输出逐级递增。每一级对应的宏数值与语义如下表:
| 级别常量 | 数值 | 语义 | 触发条件 |
|---|---|---|---|
Trace_Level_Off | 0 | 关闭所有日志输出 | 当前级别设为 0 时,所有日志方法静默跳过 |
Trace_Level_Logger | 1 | 仅输出 logger 级别(最低基础输出) | traceLevel >= 1 时 logger() 生效 |
Trace_Level_Error | 2 | 输出 Error 及以上 | traceLevel >= 2 时 error() 生效(包含 logger) |
Trace_Level_Warn | 3 | 输出 Warn 及以上 | traceLevel >= 3 时 warn() 生效(包含 error + logger) |
Trace_Level_Info | 4 | 输出 Info 及以上 | traceLevel >= 4 时 info() 生效 |
Trace_Level_Debug | 5 | 输出 Debug 及以上(全部) | traceLevel >= 5 时 debug() 和 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") 一行即可完成记录。
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]":
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。它包含两个关键设计:
- 长度限制:超过 4096 字节的数据将被丢弃,与
error()的截断保护一致。 - 隐私遮蔽:当数据长度大于 6 字节时,前 4 字节和后 3 字节正常显示为十六进制(如
A1B2C3D4),中间部分全部替换为**。这在诊断场景中保护了敏感数据(如 VIN 码、密钥材料),同时保留了首尾字节供调试定位。
// 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(),它是整个日志系统的流控制枢纽:
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 函数 | 签名 | 功能 |
|---|---|---|
logger | void logger(char* tag, char* msg) | 记录日志,自动识别 bracket 标签 |
logger_num | void logger_num(char* tag, int number) | 记录数值型日志 |
logger_hex | void logger_hex(char* tag, unsigned char* u8Buffer, int len) | 记录十六进制数据(含遮蔽) |
setLoggerLevel | void 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 触发)记录编译时间戳:
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 之后,建议继续了解以下关联模块:
- otxproxy.h:向 OTX 运行时暴露的 C 语言 API 契约 — 了解 Tracer 的 C API 如何与其他导出函数(JSON 解析、时间工具)共同构成 OTX 契约
- otxproxy.cpp:JSON 解析、时间工具与日志系统的 API 实现 — 深入查看 C API 的完整实现细节
- MyStringUtils 与 DateFormat:字符串处理与时间格式化工具 — 了解 Tracer 依赖的 UTF8ToGBK 编码转换和时间戳格式化
- 刷写器架构总览:从 ECUFlasher 接口到多 ECU 并行刷写的继承体系 — 查看 Tracer 在刷写引擎中的实际使用场景