日志系统
本页介绍 WeChat-Mini-Program-OTA 工程中由杰理 OTA/RCSP SDK 内置的日志系统:JLOTASDK 与 JLRCSP 两条日志管道的级别体系、外部 logger 注入机制、日志过滤规则,以及小程序侧的使用与扩展方式。
Purpose and Scope
本页覆盖范围:
- SDK 日志管道的架构与数据流(
jl_ota_2.1.1.js与jl_rcsp_ota_2.1.1.js中的日志实现) - 日志级别(
logv/logd/logi/logw/loge)与阈值过滤规则 setLogger()/setLogGrade()两个配置入口的设计意图- 小程序侧如何接入日志(包括适配
console与微信实时日志) - 日志相关的边界行为、性能与运维注意事项
不涉及的内容(属于其他目录页的边界):
- 蓝牙设备扫描、连接与数据收发流程(参见「蓝牙连接」相关页面)
- OTA 升级状态机与命令协议细节(参见「OTA 升级流程」相关页面)
- 用户可见的提示弹窗封装
toast/showBtStatusMsg属于util.js的 UI 工具,本页仅在「扩展点」中提及
Overview
该工程的 OTA 固件升级能力由两套 JS 库组成,形成分层结构:
jl_rcsp_ota_2.1.1.js—— RCSP(杰理自有通信协议)协议层,负责 BLE 链路上的命令封装、分包、重发、解析,标签为JLRCSP。jl_ota_2.1.1.js—— OTA 业务层,负责升级状态机(查询设备、进入升级模式、分块传输、查询结果、重连等),标签为JLOTASDK,且require了 RCSP 层。
每一层内部都内嵌了一套独立的日志管道:模块级变量持有「外部 logger 对象」与「日志级别阈值」,对外导出五个日志方法与两个配置方法。任何业务代码(如 OTAImpl 的状态机、RCSPDataHandler 的数据收发)在需要记录信息时,都直接调用本模块内部的日志函数(如 l(...)、a(...)、n(...)),这些函数统一做「级别过滤 + 委托给外部 logger」两件事。
这种设计的核心意图是:
- 解耦:SDK 自身不依赖
console或微信任何 API,宿主小程序通过setLogger()决定日志最终去向(控制台、实时日志、远程上报等),SDK 对宿主零感知。 - 性能:日志调用是热路径(BLE 收包/发包、进度回调高频触发),级别过滤在 SDK 内部先行完成,未达到阈值时根本不调用 logger 方法,避免无意义的字符串拼接与函数调用开销。
- 可观测性:开发者可以在不修改 SDK 源码的前提下,通过自定义 logger 收集升级过程的关键节点日志,用于问题定位。
两个库在仓库中存在两份拷贝:工程内引用路径 code/JLOTA/miniprogram/lib/jl_lib/ 与发布路径 libs/,内容一致(均为压缩构建产物),本页以工程内路径为准。
Architecture
flowchart TD
subgraph sg_App["小程序宿主 (miniprogram)"]
App["业务页面 / 工具代码"]
CustomLogger["自定义 Logger 对象<br/>(logv/logd/logi/logw/loge 方法)"]
end
subgraph sg_OtaLib["jl_ota_2.1.1.js (标签 JLOTASDK)"]
OTA_Logger["模块级 logger 引用 s"]
OTA_Grade["级别阈值 n (默认 1)"]
OTA_LogFns["logv/logd/logi/logw/loge 导出函数"]
OTA_Impl["OTAImpl 状态机调用点<br/>(startOTA/cancelOTA/重连/错误回调)"]
end
subgraph sg_RcspLib["jl_rcsp_ota_2.1.1.js (标签 JLRCSP)"]
RCSP_Logger["模块级 logger 引用 s"]
RCSP_Grade["级别阈值 r (默认 1)"]
RCSP_LogFns["logv/logd/logi/logw/loge 导出函数"]
RCSP_Impl["RCSPDataHandler / RcspParser 调用点<br/>(发包/收包/重发/解析)"]
end
App -->|"require 引入"| OTA_Impl
OTA_Impl -->|"内部调用"| OTA_LogFns
OTA_LogFns -->|"级别过滤: n <= 阈值?"| OTA_Logger
OTA_Impl -->|"require 依赖"| RCSP_Impl
RCSP_Impl -->|"内部调用"| RCSP_LogFns
RCSP_LogFns -->|"级别过滤: r <= 阈值?"| RCSP_Logger
App -->|"setLogger() 注入"| CustomLogger
CustomLogger -->|"委托输出"| OTA_Logger
CustomLogger -->|"委托输出"| RCSP_Logger
架构说明
- 两条独立管道:OTA 层与 RCSP 层各自持有独立的 logger 引用(压缩后均为
s)与级别阈值(OTA 层为n,RCSP 层为r)。因此两个库的setLogger()/setLogGrade()是分别调用的——只配置其一,另一层仍保持默认状态。 - 单向依赖:
jl_ota_2.1.1.js依赖jl_rcsp_ota_2.1.1.js(require("./jl_rcsp_ota_2.1.1.js")),日志同样分层:RCSP 层记录协议收发细节,OTA 层记录升级业务状态,二者通过同一个宿主 logger 输出,但以标签JLRCSP/JLOTASDK区分来源。 - 零运行时依赖:SDK 内部不 import 任何微信 API 或
console,全部输出经由注入的 logger 对象完成,因此可以运行于任意 JS 宿主(微信小程序、真机调试、单元测试环境)。
日志级别与过滤机制
级别体系
SDK 采用 Android Log 风格的 5 级日志体系,数值越小越详细:
| 导出函数 | 语义 | 内部阈值判定 | 典型场景(源码证据) |
|---|---|---|---|
logv | VERBOSE(冗余) | n <= 1 | 设备 ADV 广播信息、数据缓冲排队提示(RCSPDataHandler: 放入数据缓冲区,等待发送) |
logd | DEBUG(调试) | n <= 2 | exports.logd 导出但 OTA 层内部无直接调用点(供宿主使用) |
logi | INFO(信息) | n <= 3 | 连接初始化、inquiryDeviceCanOTA : >>>>>>>>>>>>、queryUpdateResult 结果打印 |
logw | WARN(警告) | n <= 4 | 命令重发计数提示(reSendCount)、未知 ADV 类型告警 |
loge | ERROR(错误) | n <= 5 | 协议解析失败、callbackOTAError、超时重连失败、OnSendDataCallback is null 等 |
过滤判定规则
以 RCSP 层(压缩产物)为例,模块头部定义了默认阈值与五个级别函数:
const t = "JLRCSP";
var s, e, r = 1;
function n(...e) { r <= 1 && null != s && s.logv(t, ...e) }
function i(...e) { r <= 3 && null != s && s.logi(t, ...e) }
function a(...e) { r <= 4 && null != s && s.logw(t, ...e) }
function h(...e) { r <= 5 && null != s && s.loge(t, ...e) }
// exports.logd = function(...e) { r <= 2 && null != s && s.logd(t, ...e) }
// exports.setLogGrade = function(t) { r = t }
// exports.setLogger = function(t) { s = t }
Source: jl_rcsp_ota_2.1.1.js
OTA 层(压缩产物)采用完全相同的结构,标签为 JLOTASDK、阈值变量为 n:
var t = require("./jl_rcsp_ota_2.1.1.js");
const e = "JLOTASDK";
var s, i, n = 1;
function r(...t) { n <= 1 && null != s && s.logv(e, ...t) }
function a(...t) { n <= 3 && null != s && s.logi(e, ...t) }
function l(...t) { n <= 5 && null != s && s.loge(e, ...t) }
// exports.logd = function(...t) { n <= 2 && null != s && s.logd(e, ...t) }
// exports.logw = function(...t) { n <= 4 && null != s && s.logw(e, ...t) }
// exports.setLogGrade = function(t) { n = t }
// exports.setLogger = function(t) { s = t }
Source: jl_ota_2.1.1.js
关键点:
- 双重守卫:每条日志都要同时满足「级别阈值」与「logger 已注入」两个条件,任一不满足则整条调用被短路(
&&左侧为 false 时右侧不执行)。这是刻意为之的零开销过滤——阈值过滤发生在任何字符串模板拼接之前。 - 默认阈值 1(VERBOSE):SDK 默认记录全部级别;但默认
s为undefined,即未调用setLogger之前所有日志全部丢弃。二者结合意味着:默认配置下 SDK 静默运行,只有显式注入 logger 后才开始产出日志。 - 注意:OTA 层内部仅定义了
logv/logi/loge三个内部函数(对应压缩名r/a/l),logd/logw只在exports中按同一阈值规则补齐,供宿主直接调用导出 API 时使用。
阈值与可见性对照
setLogGrade(grade) | 可见级别 | 备注 |
|---|---|---|
1 | 全部(v/d/i/w/e) | 默认值,最详细 |
2 | d/i/w/e | 隐藏 VERBOSE |
3 | i/w/e | 常见生产配置 |
4 | w/e | 仅警告与错误 |
5 | e | 仅错误 |
| 其他值 | 视比较结果而定 | SDK 不做合法性校验,传入非 1-5 数值会导致所有/部分级别被过滤,需自行保证 |
核心实现剖析
日志委托链路(以一次错误回调为例)
OTA 升级失败时,OTAImpl.D(t, e)(压缩名)会构造错误描述并调用内部 l(...)(即 loge)记录错误码(十六进制带 0x 前缀)与附加信息,随后才触发上层回调 onError:
D(t, e) {
this.v(null), this.O(), l("callbackOTAError : has an exception, code = " +
function (t) {
let e; e = t < 0 ? -t : t;
const s = e.toString(16).toUpperCase();
return "" === s ? "0x00" : t < 0 ? "-0x" + s : "0x" + s
}(t) + ", " + e),
this.m.onError(t, e), this.m.callback = null
}
Source: jl_ota_2.1.1.js
这段代码揭示了两点设计意图:
- 日志先行、回调在后:先记录完整上下文(错误码十六进制 + 描述),再触发用户回调,保证即使回调抛出异常或回调处理逻辑复杂,错误现场也已落盘/输出。
- 日志与业务解耦:
D()同时负责状态清理(v(null)、O())、日志记录、回调通知三个职责,日志只是旁路,不影响主流程。
RCSP 协议层的高频日志点
协议层在数据收发热路径上埋有大量 logv/logw 日志,是排查 BLE 链路问题的主要依据:
// 发送队列:入队等待发送
W(t) {
if (this.B(t)) {
if (this.H.push(t), this.H.length > 1)
return void n("RCSPDataHandler: 放入数据缓冲区,等待发送");
const s = { complete: () => { this.X(this.H, s) } };
this.X(this.H, s)
}
}
// 重发超时:记录重发计数
X(t, s) {
// ...
let t = setTimeout((t => {
const s = t, e = s._;
a("RCSPDataHandler: " + s.command + ", reSendCount: " + e + ", limit: " + this.T);
// ... 未达上限则重发,达到上限则回调 ERROR_RESPONSE_TIMEOUT
}), e.timeoutMs, e);
}
Source: jl_rcsp_ota_2.1.1.js
reSendCount 日志属于 WARN 级(a = logw),用于提示链路不稳定、命令重发中;若连续重发超过上限(limit: 3)则升级为 ERROR_RESPONSE_TIMEOUT 错误并通过 loge 记录。这样的分级让开发者可以依据日志级别快速筛选问题:WARN 级大量出现说明信道质量差,ERROR 级出现则说明命令已失败。
字符串与二进制数据的日志化
SDK 还导出了若干辅助函数,用于在日志中展示二进制数据,属于日志系统的「格式化工具层」:
ab2hex(buffer):ArrayBuffer/Uint8Array转十六进制字符串(如设备信息、license)。toHexWithPrefix(value)(RCSP 层):负数取绝对值后输出-0x…,非负数输出0x…,用于日志中展示错误码。hexDataCovetToAddress(RCSP 层):字节数组转大写冒号分隔的 MAC 地址格式,用于日志中展示设备地址。
这些函数在 SDK 内部日志调用中高频使用(例如 fillTargetInfo: number:0 value: …、callbackOTAError : code = -0x67),保证二进制信息以人类可读形式进入日志流。
使用示例
基础用法:注入 console 作为 logger
两个库都导出 setLogger,宿主可在 app.js 或工具模块中一次性注入。由于 console 自带 log/info/warn/error 方法,而 SDK 要求的 logger 接口是 logv/logd/logi/logw/loge,直接传 console 只能命中部分级别,推荐做一层适配:
// utils/logger.js —— 宿主侧日志适配层(示意)
const jlRcsp = require('../lib/jl_lib/jl_rcsp_ota_2.1.1.js');
const jlOta = require('../lib/jl_lib/jl_ota_2.1.1.js');
const sdkLogger = {
logv(tag, ...args) { console.log(`[${tag}]`, ...args) },
logd(tag, ...args) { console.log(`[${tag}]`, ...args) },
logi(tag, ...args) { console.info(`[${tag}]`, ...args) },
logw(tag, ...args) { console.warn(`[${tag}]`, ...args) },
loge(tag, ...args) { console.error(`[${tag}]`, ...args) }
};
// 注意:两条管道必须分别注入
jlRcsp.setLogger(sdkLogger);
jlOta.setLogger(sdkLogger);
// 建议同时将级别收敛到 INFO(3),避免 VERBOSE 刷屏
jlRcsp.setLogGrade(3);
jlOta.setLogGrade(3);
Source: jl_rcsp_ota_2.1.1.js · jl_ota_2.1.1.js
高级用法:对接微信实时日志(RealtimeLogManager)
生产环境排障时,可将 SDK 日志委托给 wx.getRealtimeLogManager(),使 JLOTASDK/JLRCSP 标签日志进入微信实时日志系统(支持真机远程查看):
const realtimeLogger = {
logv(tag, ...args) { wx.getRealtimeLogManager().info(tag, ...args) },
logd(tag, ...args) { wx.getRealtimeLogManager().info(tag, ...args) },
logi(tag, ...args) { wx.getRealtimeLogManager().info(tag, ...args) },
logw(tag, ...args) { wx.getRealtimeLogManager().warn(tag, ...args) },
loge(tag, ...args) { wx.getRealtimeLogManager().error(tag, ...args) }
};
// 注入后,OTA 升级全过程的关键节点均可在微信开发者工具/实时日志面板回溯
Source: jl_ota_2.1.1.js
说明:
wx.getRealtimeLogManager()属于宿主侧 API,SDK 本身不感知。上述示例展示的是「利用setLogger扩展点把 SDK 日志接入微信基础设施」的典型做法;具体 API 以微信官方文档为准。
直接调用导出日志 API
宿主业务代码也可以直接使用两个库导出的 logv/logd/logi/logw/loge(它们会自动携带 SDK 标签):
const otaLib = require('../lib/jl_lib/jl_ota_2.1.1.js');
otaLib.logi('用户点击了升级按钮,开始准备 OTA 配置'); // 输出标签为 JLOTASDK
otaLib.loge('升级文件读取失败');
Source: jl_ota_2.1.1.js
用户可见反馈通道(非日志)
util.js 提供了面向用户的弹窗与蓝牙错误提示封装,与日志系统互补:日志面向开发者(可追溯、可分级),toast/showBtStatusMsg 面向用户(即时反馈):
export function showBtStatusMsg(code, errMsg) {
switch (code) {
case 10000: toast('未初始化蓝牙适配器'); break;
case 10001: toast('未检测到蓝牙,请打开蓝牙重试!'); break;
case 10002: toast('没有找到指定设备'); break;
case 10003: toast('连接失败'); break;
// ... 其余系统错误码映射
default: toast(errMsg);
}
}
Source: util.js
设计意图:SDK 日志管道负责「发生了什么」(技术细节),showBtStatusMsg 负责「用户该怎么办」(可操作的提示),二者在集成时通常配合使用——先记日志定位,再弹提示引导。
配置选项
| 选项 | 类型 | 默认值 | 作用范围 | 说明 |
|---|---|---|---|---|
setLogGrade(grade) | number | 1(VERBOSE) | 单条管道独立 | 设置日志级别阈值,低于阈值的日志被过滤;OTA 层变量 n、RCSP 层变量 r,互不影响 |
setLogger(logger) | object | null | 单条管道独立 | 注入外部 logger 对象,需提供 logv/logd/logi/logw/loge 方法;未注入时所有日志静默丢弃 |
两个库必须分别调用上述方法(共 4 次调用)才能完整开启日志。仓库内无 appsettings/环境变量类配置,日志行为完全由代码注入决定。
API 参考
以下 API 同时存在于 jl_ota_2.1.1.js 与 jl_rcsp_ota_2.1.1.js 的导出中(OTA 层标签 JLOTASDK,RCSP 层标签 JLRCSP)。
setLogger(logger): void
注入日志输出目标。
参数:
logger(object):需实现logv(tag, ...args)、logd(tag, ...args)、logi(tag, ...args)、logw(tag, ...args)、loge(tag, ...args)五个方法;tag由 SDK 传入(JLOTASDK或JLRCSP)。
返回: 无。
说明: 内部实现为 s = t(直接赋值模块级引用)。重复调用会覆盖之前的 logger。传入 null/undefined 可关闭日志输出。
setLogGrade(grade): void
设置日志级别阈值。
参数:
grade(number):1=VERBOSE,2=DEBUG,3=INFO,4=WARN,5=ERROR。
返回: 无。
说明: 内部实现为 n = t(OTA 层)/ r = t(RCSP 层)。SDK 不做取值校验,需由调用方保证范围。
logv(...args) / logd(...args) / logi(...args) / logw(...args) / loge(...args): void
按级别输出日志。
参数: 任意可打印参数,SDK 内部调用时第一个参数通常为字符串消息(标签由 SDK 自动附带)。
返回: 无。
行为: 级别号 ≤ 当前阈值且已注入 logger 时才调用 logger.logX(tag, ...args);否则静默返回。注意 SDK 内部调用不经过导出函数(直接调用内部闭包),因此即使宿主只使用导出 API,内部日志也照常工作。
失败模式、边界情况与并发
logger 未注入(最常见的「日志丢了」场景)
默认 s = null,未调用 setLogger 前所有日志静默丢弃。排查日志缺失时,第一步应确认两条管道都已注入(jlRcsp.setLogger(...) 与 jlOta.setLogger(...) 缺一不可),且注入发生在 SDK 产生日志之前(建议在 app.js 启动早期完成)。
级别阈值设置不当导致日志「过少」或「过多」
- 设为
5时仅保留 ERROR,升级成功的正常路径(onRcspInit、queryUpdateResult等 INFO 日志)全部不可见,无法确认流程走到了哪一步。 - 保持默认
1且接入实时日志时,VERBOSE 级高频日志(数据缓冲、ADV 信息)会产生大量噪音并占用实时日志配额。 - SDK 不做范围校验,传入
0或负数会导致全部级别被过滤;传入大于5的值则只有loge可能可见。
版本分裂风险:libs/ 与 code/ 双拷贝
仓库同时存在 libs/jl_ota_2.1.1.js、libs/jl_rcsp_ota_2.1.1.js 与 code/JLOTA/miniprogram/lib/jl_lib/ 下的同名文件,当前内容一致(压缩构建产物)。若后续升级 SDK,必须保证两处同步更新,否则会出现「工程内引用的代码与发布包不一致」的排查陷阱;日志标签与版本号(2.1.1)是判断文件版本的第一线索。
日志回调抛异常
SDK 对 logger 方法的调用没有 try/catch 保护。若宿主 logger 实现抛异常(如实时日志 API 在低版本基础库不可用),异常会沿日志调用点向上传播,可能中断 RCSPDataHandler 的数据处理循环。建议宿主 logger 内部自行兜底:
function safeCall(fn, ...args) {
try { fn(...args) } catch (e) { /* 日志失败绝不影响主流程 */ }
}
并发与重入
SDK 为单线程 JS 模型,日志调用天然串行,无锁竞争问题。但两个库共享同一个宿主 logger 实例时,日志会以标签区分来源而非进程区分;若宿主在 logger 中做异步上报(如 wx.request 上传日志),需自行处理顺序与去重,SDK 不提供日志缓冲/批量上报能力。
高频路径上的字符串拼接
loge/logi 在错误回调、进度通知等路径上会拼接大段字符串(如 JSON.stringify(deviceInfo))。虽然阈值过滤发生在拼接之前(守卫短路),但一旦达到阈值,拼接开销即产生。生产环境建议将阈值收敛到 3(INFO)及以上,避免 VERBOSE/DEBUG 级的大对象序列化拖慢 BLE 数据处理热路径。
性能与运维注意事项
- 过滤先行:SDK 的
n <= X && s.logX(...)短路求值保证未达阈值的日志零成本,这是日志设计中最值得保持的性能特性;宿主 logger 不应再重复做级别判断。 - 推荐生产配置:
setLogGrade(3)+ 实时日志/远程上报 logger,兼顾可观测性与性能;仅在真机联调阶段临时降为1。 - 日志量预估:一次完整 OTA 升级会产生数百条协议级日志(每块文件传输、每次重发计数),其中 VERBOSE 占大头;对日志有配额限制的平台(微信实时日志)需留意。
- 发布包同步:升级 SDK 时同步更新
libs/与code/两份拷贝,并以文件内2.1.1版本号核对。
扩展点
- 自定义 Logger:
setLogger是唯一的日志出口扩展点,可对接console、wx.getRealtimeLogManager()、wx.request远程日志、文件系统落盘等任意目标,且无需改动 SDK。 - 级别策略:
setLogGrade支持运行时动态调整,可依据调试开关/用户设置切换详细程度。 - 标签路由:宿主 logger 收到
JLOTASDK/JLRCSP标签后可按来源路由(如只采集 OTA 层、忽略协议层),实现精细化的采集策略。 - 配套 UI 提示:结合
util.js的toast/showBtStatusMsg(util.js)将错误码映射为用户可读提示,形成「开发者日志 + 用户提示」双通道。
测试
仓库未包含针对日志系统的独立单元测试文件;日志行为由 SDK 运行期自检(如 g 类构造时 null == s && l("IHandleResult is null.") 这类内部断言日志)间接验证。实际验证方式以真机/开发者工具控制台观察 JLOTASDK/JLRCSP 前缀日志为准。集成时的自检清单:
- 注入 logger 后触发一次蓝牙连接,确认
JLRCSP标签出现onRcspInitINFO 日志; - 触发一次 OTA 升级,确认
JLOTASDK标签出现inquiryDeviceCanOTA、queryUpdateResult等节点日志; - 调低
setLogGrade(5)后复现一次失败升级,确认仅 ERROR 级输出且callbackOTAError带十六进制错误码。
Related Links
- OTA SDK 业务层(日志标签 JLOTASDK)
- RCSP 协议层(日志标签 JLRCSP)
- UI 提示工具 util.js(toast / showBtStatusMsg)
- 发布版 SDK 副本 libs/
- 相关目录页:OTA 升级流程、蓝牙连接、RCSP 命令协议(参见目录导航中的对应页面)