杰理 SDK 文档中心
首页
首页
  • SDK 框架库

    • JL_BLEKit 蓝牙通信核心
    • JL_AdvParse 广播包解析
    • JL_HashPair 加密配对
    • JL_OTALib 固件升级
    • JLDialUnit 彩屏仓与表盘控制
    • JLBmpConvertKit 位图转换
    • JLPackageResKit 资源包处理
    • JLLogHelper 日志工具
  • 核心功能模块

    • 音乐与媒体控制
    • 音效调节与均衡器
    • 设备发现、连接与设置
    • Auracast 广播接收与发射
    • 文件浏览、闹钟、FM 与灯光控制
    • ANC、按键设置与查找设备
    • AI 翻译与自定义命令
  • 应用架构与工程支撑

    • 杰理之家 App 架构与导航
    • 数据存储与缓存
    • Swift 工具与扩展层
    • JLAudioUnitKit 示例工程
    • SDKTestHelper 测试工具
  • 开发文档与资源

    • 文档中心与 JL_OTALib API 说明
    • 自定义蓝牙接入方式
    • 调试技巧与问题排查
    • 版本历史与社区支持

JLLogHelper 日志工具

JLLogHelper 是杰理科技(Jieli)iOS 蓝牙 SDK 系列中的统一日志辅助框架,以 JLLogManager 单例式类方法 API 为核心,提供带等级过滤、函数名/行号定位、时间戳、文件保存、路径重定向与日志收集回调等能力的轻量级日志打印解决方案。

Purpose and Scope

本页介绍 JLLogHelper.xcframework 的完整能力:

  • JLLogManager 核心类的全部类方法 API 与设计意图;
  • kJLLog 打印宏与 JLLOG_LEVEL 日志等级的定义与语义;
  • 日志从调用点到控制台/文件/回调的完整数据流;
  • 配置项、失败模式、并发与性能考量、扩展方式。

相关但属于其他目录页的主题不在本页展开:音频处理框架请参见 JLAudioUnitKit 相关页面;图像转换请参见 JLBmpConvertKit;资源包处理请参见 JLPackageResKit。本页只聚焦日志框架本身的机制与用法。

Overview

JLLogHelper 是整个 iOS-JL_Bluetooth 仓库中被 JieLi_Home_Demo 等示例工程统一引用的日志基础库(见 JieLiAppHeader.pch 中的 #import <JLLogHelper/JLLogHelper.h>)。其设计目标可以概括为三点:

  1. 零成本接入:业务代码只需 #import <JLLogHelper/JLLogHelper.h>,即可通过 kJLLog 宏或 JLLogManager 类方法打印日志,无需额外初始化。
  2. 可分级、可定位:日志分五级(JLLOG_COMPLETE 到 JLLOG_ERROR),可整体使能/禁用,并可选择在输出中附带 函数名 + 行号,便于在蓝牙协议栈这类回调密集的代码中快速定位问题。
  3. 可持久化、可采集:支持把日志保存为文件、重定向保存路径、按时间戳打印,以及通过 block 回调把日志实时转发给上层(例如用于 SDK 联调抓包或上传崩溃现场)。

框架以静态库 + 头文件的方式打包为 JLLogHelper.xcframework,支持 iOS(arm64)、iOS 模拟器(arm64 + x86_64)与 macOS(arm64 + x86_64)三种平台切片,由 libs/JLLogHelper.xcframework/Info.plist 声明。

Architecture

flowchart TD
    subgraph sg_App["应用层 (App Code)"]
        Macro["kJLLog 打印宏<br/>自动携带 __FUNCTION__ / __LINE__"]
        Direct["JLLogManager 类方法调用<br/>logSomething / logObject / logLevel:content:"]
    end

    subgraph sg_Framework["JLLogHelper.framework"]
        Umbrella["JLLogHelper.h<br/>入口头文件 + 版本符号"]
        Manager["JLLogManager<br/>日志核心类(类方法)"]
        Level["JLLOG_LEVEL 枚举<br/>COMPLETE~ERROR"]
    end

    subgraph sg_Output["日志输出目标"]
        Console["控制台打印"]
        LogFile["日志文件存储<br/>saveLog / openLogTextFile"]
        Callback["collectLog block 回调"]
    end

    Macro -->|"展开为 logLevel:funcName:line:format:"| Manager
    Direct --> Manager
    Umbrella --> Manager
    Level --> Manager
    Manager --> Console
    Manager --> LogFile
    Manager --> Callback

架构要点说明:

  • 入口收敛:JLLogHelper.h 是框架唯一的伞头文件(umbrella header),对外只暴露 JLLogManager.h,同时导出 JLLogHelperVersionNumber 与 JLLogHelperVersionString 两个版本符号,供上层判断框架版本。
  • 类方法设计:JLLogManager 的所有能力均为 + 类方法,内部以单例/静态状态持有配置(使能开关、等级、时间戳、文件保存开关、保存路径、收集回调等),因此不需要实例化,天然全局唯一,符合日志组件的定位。
  • 宏是唯一推荐入口:kJLLog 宏借助编译器内置的 __FUNCTION__ 与 __LINE__ 自动注入调用位置,是 setLog:IsMore: 中"是否打印函数名与行号"能力的实现基础;直接调用类方法也可以,但需要手动传函数名和行号。

核心实现解析

日志等级枚举 JLLOG_LEVEL

等级定义见 JLLogManager.h:

typedef NS_ENUM(NSInteger, JLLOG_LEVEL) {
    JLLOG_COMPLETE  = 0,
    JLLOG_DEBUG     = 1,
    JLLOG_INFO      = 2,
    JLLOG_WARN      = 3,
    JLLOG_ERROR     = 4,
};

设计语义:

等级值用途
JLLOG_COMPLETE0全量日志(不过滤),适合协议抓包、原始数据流跟踪
JLLOG_DEBUG1调试信息,框架默认等级
JLLOG_INFO2常规运行信息
JLLOG_WARN3警告,不阻塞流程但需关注
JLLOG_ERROR4错误,通常伴随异常路径或失败返回

等级值从小到大对应"严重程度"递增、日志量递减。setLog:IsMore:Level: 设置的等级作为过滤阈值:只有 level >= 阈值 的日志才会被输出,JLLOG_COMPLETE 因此是"输出一切"的特殊档位。

核心数据流与过滤机制

kJLLog 宏的展开

宏定义见 JLLogManager.h:

#define kJLLog(level,fmt...) [JLLogManager logLevel:level funcName:__FUNCTION__ line:__LINE__ format:fmt]

这是典型的可变参数转发宏:fmt... 原样透传给 logLevel:funcName:line:format:... 的可变参数,__FUNCTION__ 和 __LINE__ 在宏展开处求值,因此无论日志代码位于哪个类、哪一行,输出都能精确指向调用点。这也意味着宏只能用于 Objective-C 函数/方法上下文;Swift 侧建议直接调用 logLevel:content: 或 logSomething: 类方法。

日志过滤与输出流程

flowchart TD
    Start(["调用 kJLLog / logLevel"]) --> CheckEnable{"enable == YES?"}
    CheckEnable -->|"NO"| Drop1(["直接丢弃,零开销返回"])
    CheckEnable -->|"YES"| CheckLevel{"传入 level >= 设定阈值?"}
    CheckLevel -->|"NO"| Drop2(["低于阈值,丢弃"])
    CheckLevel -->|"YES"| Build["拼接前缀<br/>时间戳(可选) + 函数名/行号(可选)"]
    Build --> Console["控制台打印"]
    Console --> IsSave{"saveLogAsFile == YES?"}
    IsSave -->|"YES"| File["写入日志文件<br/>(redirectLogPath 指定目录)"]
    IsSave -->|"NO"| Done
    File --> Done(["结束"])
sequenceDiagram
    participant App as 业务代码
    participant M as JLLogManager
    participant C as 控制台
    participant F as 日志文件
    participant CB as 上层回调

    App->>M: kJLLog(JLLOG_INFO, "BT connect %@", addr)
    M->>M: 过滤:enable/等级/前缀拼接
    M->>C: 控制台输出
    alt saveLogAsFile == YES
        M->>F: saveLog: 追加写入文件
    end
    opt 已设置 collectLog:
        M-->>CB: block(log 字符串) 实时转发
    end

两条路径的设计意图:

  • 先过滤后格式化:等级与使能判断在字符串拼接之前完成,低等级日志被直接丢弃,避免无谓的字符串格式化开销——这在蓝牙音频等高频率日志场景中很重要。
  • 输出解耦:控制台、文件、回调三路输出相互独立。collectLog: 的回调不参与文件写入,上层既可只监听、也可同时开启文件保存,互不阻塞。

配置选项

JLLogHelper 的配置全部通过 JLLogManager 类方法在运行时设置,无独立配置文件。常用配置项如下:

配置项方法类型/取值默认值说明
日志使能与等级setLog:IsMore:Level:enable: BOOL;isMore: BOOL;level: JLLOG_LEVEL开启;JLLOG_DEBUG总开关 + 等级阈值 + 是否打印函数名/行号
时间戳打印logWithTimestamp:BOOLNO是否在日志前缀中打印时间
文件保存saveLogAsFile:BOOLYES是否把日志同时保存为文件
保存路径redirectLogPath:NSString 路径框架默认路径重定向日志文件保存目录
日志收集回调collectLog:void(^)(NSString *log)nil(未设置)每条日志实时回调给上层
打印 SDK 版本sdkVersion——输出框架版本信息
清理日志clearLog——清空已存储的日志
打开日志文本openLogTextFile——打印文本日志(便于查看文件内容)

API Reference

全部接口均为类方法,定义于 JLLogManager.h。

+ (void)sdkVersion

打印当前 JLLogHelper SDK 版本到控制台。用于集成自检,确认 Demo 与发布包版本一致。

+ (void)clearLog

清理已存储的日志文件内容。在开启 saveLogAsFile: 的场景下,可用于分段抓取日志(例如每轮设备连接前清空一次)。

+ (void)openLogTextFile

打开/打印文本日志。配合文件保存使用,便于直接查看当前日志文件内容。

+ (void)redirectLogPath:(NSString *)path

参数: path(NSString)——日志文件保存目录,需为可写路径。

重定向日志文件保存位置。默认路径由框架内部管理;当 App 需要把日志统一收进自己的沙盒目录(如 Documents/Logs)以便上传或备份时必须调用此方法。

+ (void)collectLog:(void(^)(NSString *log))block

参数: block——日志回调,接收格式化后的完整日志字符串。

注册日志收集回调。设置后每条通过框架输出的日志都会以字符串形式回调给上层;传入 nil 可取消。典型用途:SDK 联调时把日志转发到自定义显示控件,或在崩溃前把日志批量上报。

+ (void)logWithTimestamp:(BOOL)isTimeStamp

参数: isTimeStamp(BOOL)——是否打印时间戳,默认 NO。

开启后日志输出会携带时间前缀,便于分析耗时问题(如蓝牙连接时序、音频数据缓冲节奏)。

+ (void)saveLogAsFile:(BOOL)isSave

参数: isSave(BOOL)——是否保存为文件,默认 YES。

控制日志是否同时写入文件。发布版可置 NO 以减少 IO;联调期保持 YES 以便追溯历史日志。

+ (void)setLog:(BOOL)enable IsMore:(BOOL)isMore Level:(JLLOG_LEVEL)level

参数:

  • enable(BOOL)——日志总开关,NO 时所有日志丢弃;
  • isMore(BOOL)——是否打印【函数名 & 行号】;
  • level(JLLOG_LEVEL)——日志等级阈值,低于该等级的日志不输出。

设计意图: 这是框架最核心的运行时开关。isMore 依赖 kJLLog 宏注入的 __FUNCTION__/__LINE__,若直接调用 logLevel:content: 则无法附带函数名行号。

+ (void)logLevel:(JLLOG_LEVEL)level funcName:(const char * _Nullable)func line:(const int)line format:(NSString * _Nonnull)format, ...

参数:

  • level(JLLOG_LEVEL)——本条日志等级;
  • func(const char *,可空)——函数名;
  • line(int)——行号;
  • format(NSString *)——格式化字符串,后接可变参数。

kJLLog 宏的目标方法,是框架内部统一的打印入口。业务代码通常不应直接调用,而应使用宏以保证函数名/行号正确注入。

+ (void)logSomething:(NSString *)something

参数: something(NSString *)——任意字符串内容。

无等级、无格式化的简易打印,适合临时打点。

+ (void)logObject:(id)object

参数: object(id)——任意对象。

直接打印对象(内部走 description)。适合快速输出字典、数组、模型对象。

+ (void)logLevel:(JLLOG_LEVEL)level content:(NSString *)content

参数:

  • level(JLLOG_LEVEL)——日志等级;
  • content(NSString *)——已格式化的完整内容。

Swift 桥接友好的带等级打印入口,不携带函数名/行号信息。

+ (void)saveLog:(NSString *)logContent

参数: logContent(NSString *)——日志内容。

手动把一段日志内容写入日志文件,不经过控制台输出。可用于把自定义业务事件(如外设状态机切换)追加进日志文件,保持日志时间线的完整性。

使用示例

集成方式

在工程的预编译头文件(.pch)中全局引入,即可在任意文件中直接使用宏(Demo 工程即采用此方式,见 JieLiAppHeader.pch):

#import <JLLogHelper/JLLogHelper.h>

基础用法:宏打印

// 全量日志:不过滤,用于协议抓包
kJLLog(JLLOG_COMPLETE, @"raw data: %@", hexData);

// 调试日志(框架默认等级)
kJLLog(JLLOG_DEBUG, @"device connected, name = %@", deviceName);

// 错误日志
kJLLog(JLLOG_ERROR, @"send command failed, code = %d", code);

宏自动携带 __FUNCTION__ 与 __LINE__,配合 setLog:IsMore: 可输出形如 [ClassName methodName:line] 的定位前缀。

初始化与开关配置

// 开启日志,打印函数名/行号,等级阈值设为 INFO(DEBUG 以下被过滤)
[JLLogManager setLog:YES IsMore:YES Level:JLLOG_INFO];

// 开启时间戳,便于分析时序
[JLLogManager logWithTimestamp:YES];

// 把日志文件重定向到 App 沙盒目录
NSString *logDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
[JLLogManager redirectLogPath:[logDir stringByAppendingPathComponent:@"Logs"]];

// 注册收集回调:把日志实时转发给 UI 或上报模块
[JLLogManager collectLog:^(NSString *log) {
    // 例如:追加到调试悬浮窗 / 写入上传队列
}];

// 确认 SDK 版本
[JLLogManager sdkVersion];

简易打印与对象打印

// 任意字符串打点
[JLLogManager logSomething:@"enter scan flow"];

// 直接打印对象(内部走 description)
[JLLogManager logObject:connectedDevice];
[JLLogManager logObject:@{@"state": @(state), @"rssi": @(rssi)}];

// Swift 友好的带等级入口
[JLLogManager logLevel:JLLOG_WARN content:@"battery low"];

失败模式、边界情况与并发

  • 等级语义陷阱:JLLOG_COMPLETE = 0 是"最低严重度、最大日志量"的档位。若在 setLog:IsMore:Level: 中误把阈值设为 JLLOG_COMPLETE,则所有等级(含 ERROR)都会输出,日志量可能急剧膨胀,影响性能与文件体积。
  • isMore 依赖宏:只有通过 kJLLog 宏打印才能获得函数名/行号;直接调用 logLevel:content: 或 logSomething: 时,即使 isMore 为 YES 也没有位置信息可注入,排查时需注意区分。
  • 回调重入:collectLog: 的 block 在日志打印路径中同步执行。若在 block 内部再次调用日志打印(例如把日志格式化为新字符串后再 logSomething:),会形成无限递归导致栈溢出。上层应只做转发/存储,不做二次打日志。
  • 文件 IO 失败:redirectLogPath: 若传入不可写目录(如只读 bundle 路径),文件保存会静默失败或抛异常;应在写入前确认目录存在且可写,必要时 clearLog 重试。
  • 并发访问:JLLogManager 全局状态(开关、等级、路径、回调)由类方法读写,若在多个线程同时配置与打印,存在竞争条件。日志打印本身通常不保证线程安全顺序,多线程场景下日志行序可能交错;建议在配置阶段(启动早期)集中设置,运行期只做打印。
  • 发布版本号校验:JLLogHelper.h 导出的 JLLogHelperVersionNumber / JLLogHelperVersionString 用于版本自检;集成时若日志行为与文档不符,可先通过 sdkVersion 确认为最新版。

性能与运维考量

  • 过滤前置:等级过滤先于字符串格式化,是框架性能设计的核心——低频关注点(如每次回调的 logObject:)可放心使用,高频点(如音频帧、BLE 数据包)建议用 JLLOG_COMPLETE/JLLOG_DEBUG 并在发布前调高阈值或 setLog:NO 关闭。
  • 文件 IO:saveLogAsFile: 默认 YES,会在每次日志时追加写文件;长期运行时建议按需开启(联调期),发布版置 NO,或配合 redirectLogPath: 将日志落到便于清理的目录,并用 clearLog 分段管理。
  • 多平台切片:框架通过 JLLogHelper.xcframework 同时提供 ios-arm64、ios-arm64_x86_64-simulator、macos-arm64_x86_64 切片(见 Info.plist),真机、模拟器、Mac Catalyst 场景均可直接集成;打包发布时使用 Release/Libs/JLLogHelper.xcframework 并设置 Embed & Sign。
  • 日志脱敏:collectLog: 会把日志原样透传给上层,若日志中包含 MAC 地址、设备名称等敏感信息,上层回调中需自行脱敏后再上传。

扩展点

  • 输出通道扩展:框架通过 collectLog: 回调提供了唯一的自定义输出扩展点,上层可以实现日志悬浮窗、滚动视图、网络上报、崩溃文件附加等任意下游,而无需修改框架本身。
  • 路径策略定制:redirectLogPath: 允许把日志归入 App 自己的沙盒体系,配合 clearLog / openLogTextFile 可实现按会话、按设备的分段日志管理。
  • Swift 互操作:logLevel:content:、logSomething:、logObject: 等无可变参数的方法对 Swift 调用友好,Swift 工程可用 JLLogManager.logLevel(JLLOG_LEVEL.info, content: "...") 的方式接入。

Related Links

  • JLLogManager.h(API 定义)
  • JLLogHelper.h(框架入口头文件)
  • JieLiAppHeader.pch(Demo 集成方式)
  • JLLogHelper.xcframework Info.plist(平台切片声明)
  • README.md(仓库目录结构与框架清单)
  • 相关框架:JLAudioUnitKit(音频处理)、JLBmpConvertKit(图像转换)、JLPackageResKit(资源包处理)——详见各自的 SDK 框架目录页。
Prev
JLPackageResKit 资源包处理