iOS 真机日志怎么看?用 Logger 打日志并过滤导出真机日志
最后更新
iOS 真机日志的看法是:代码里用 Logger 按 subsystem 和 category 打日志,调试时在 VS Code「输出 → KXBuild Console」里看,不调试时用 kxdevice ostrace 按子系统、级别、进程过滤,再导出到文件,和崩溃报告一起分析。日常调试用 print 就够;要在不连调试器时也能查、能按模块过滤、能区分严重程度,改用 Logger(iOS 14 起可用)。Logger 的规则以 Apple 官方文档为准,截至 2026 年 10 月。
你需要一台已经能用 KXBuild 安装调试的 iPhone,连接和信任的步骤见设备准备。
print、NSLog、Logger 选哪个?
三种写法都能在调试时看到输出,区别在于不调试时还能不能看到,以及能不能过滤。
| 写法 | 写到哪里 | 调试时(KXBuild Console) | 不调试时(kxdevice ostrace) | 按模块和级别过滤 |
|---|---|---|---|---|
print |
标准输出 | 能看到 | 看不到 | 不能 |
NSLog |
系统日志 | 能看到 | 能看到 | 只能按消息文字 |
Logger / os_log |
系统日志,带子系统、分类、级别 | 能看到 | 能看到 | 能 |
print 只写到标准输出,不进系统日志,App 从主屏幕启动、没有调试器时,这些输出就丢了。Logger 写进 Apple 的统一日志系统,每条都带子系统、分类和级别,系统按级别决定只放内存还是写入磁盘,并以压缩的二进制格式存储,查看时才转换成文字。
用法上的建议是:临时排查、看完就删的输出用 print;要留在代码里、给测试同事复现问题时看的日志用 Logger。
怎么用 Logger 写日志?
Logger 在 os 模块里,从 iOS 14 起可用。
创建 Logger:subsystem 和 category
每个 Logger 带两个字符串,用来在查日志时把无关的消息筛掉。
- subsystem。标识 App 里一大块功能,Apple 建议用反向域名,通常直接用 Bundle ID,例如
com.example.myapp。 - category。标识这个子系统里的某个模块,例如
network、database、ui,命名方式自己定。
把 Logger 集中定义在一个文件里,各处直接引用,名字就不会写乱。
1import os
2
3extension Logger {
4 private static let subsystem = Bundle.main.bundleIdentifier ?? "com.example.myapp"
5
6 static let network = Logger(subsystem: subsystem, category: "network")
7 static let database = Logger(subsystem: subsystem, category: "database")
8 static let ui = Logger(subsystem: subsystem, category: "ui")
9}
用的时候调用对应级别的方法。
1func loadFeed() async {
2 Logger.network.info("开始请求首页数据")
3 do {
4 let items = try await api.fetchFeed()
5 Logger.network.notice("首页数据返回 \(items.count) 条")
6 } catch {
7 Logger.network.error("首页请求失败:\(error.localizedDescription, privacy: .public)")
8 }
9}
五个级别的区别
Apple 的Generating log messages from your code说明,系统先把所有消息放在内存里,级别越严重的越会写到磁盘。下表按严重程度从低到高排列,「是否写入磁盘」一列是 Apple 原文的意思。
| Logger 方法 | 级别 | 是否写入磁盘 | 用途 |
|---|---|---|---|
debug |
Debug | 否 | 只在开发调试时有用的详细信息 |
info |
Info | 只有用 log 工具收集时才写 | 有帮助但不是必需的排查信息 |
notice、log |
Notice(Default) | 是,有存储上限 | 排查问题必需的信息,例如可能导致失败的情况 |
error |
Error | 是,有存储上限 | 代码执行中遇到的错误 |
fault |
Fault | 是,有存储上限 | 代码里的缺陷和不该发生的状态 |
几点要记住。
- 磁盘上的日志超过预设大小后,系统会清掉旧消息,所以不要指望很久以前的日志一直都在。
- debug 级别开销很低,可以多写;fault 和 error 会让系统额外收集信息,开销更大,不要拿它们打普通流程。
- Logger 还有
trace、warning、critical三个方法,按 Apple 文档分别等同于debug、error、fault。
隐私修饰 privacy
Apple 文档写明,插值里的整数、浮点数和布尔值默认显示,动态字符串和复杂对象默认会被隐去,防止用户的账号、手机号之类的信息出现在日志文件里。
1let account = "user@example.com"
2let screen = "设置页"
3
4Logger.ui.notice("进入页面 \(screen, privacy: .public)") // 显示原文
5Logger.ui.notice("当前账号 \(account)") // 默认隐去
6Logger.ui.notice("当前账号 \(account, privacy: .private(mask: .hash))") // 隐去,但同一个值的哈希相同
- 确定不敏感的字符串,例如页面名、错误描述、接口路径,加
privacy: .public,否则在设备日志里看不到内容。 - 账号、令牌、位置这类数据保持默认,或者明确写
.private。 - 想知道几条日志是不是同一个用户,又不想暴露原值时,用
.private(mask: .hash)。
可选项的完整列表见 OSLogPrivacy。
Objective-C 里用 os_log
Objective-C 用 os_log_create 创建日志对象,按级别调用对应的函数,字符串参数用 %{public}@ 标成公开。
1#import <os/log.h>
2
3static os_log_t NetworkLog(void) {
4 static os_log_t log;
5 static dispatch_once_t once;
6 dispatch_once(&once, ^{
7 log = os_log_create("com.example.myapp", "network");
8 });
9 return log;
10}
11
12os_log_info(NetworkLog(), "开始请求 %{public}@", url.path);
13os_log_error(NetworkLog(), "请求失败,状态码 %d", statusCode);
调试时 iOS 真机日志在哪看?
按 F5 构建并运行后,App 里 print、NSLog、os_log 的输出显示在 VS Code「输出」面板的 KXBuild Console 通道,构建日志在 KXBuild 通道。
- 按 Ctrl+Shift+U 打开「输出」面板。
- 在面板右上角的下拉框里选 KXBuild Console。
- 用面板右上角的筛选框输入关键字,只显示包含它的行。
调试结束会退出手机上的 App,KXBuild Console 也就不再有新输出。要看 App 不在调试时的日志,或者要按级别、子系统筛选,用下一节的 kxdevice。
App 不在调试时,kxdevice ostrace 怎么过滤?
kxdevice ostrace 实时输出手机上的 os_log,NSLog 和 Logger 都经过它,比 syslog 干净,支持过滤。它不需要 App 在调试中,从主屏幕点开 App 也能看到。
1kxdevice ostrace --subsystem com.example.myapp
按 Ctrl-C 停止。可用的过滤参数如下,以 kxdevice 命令参考为准。
| 参数 | 作用 |
|---|---|
--levels default,info,debug,error,fault |
只输出这些级别,逗号分隔 |
--pid N |
只看这个进程,-1 为全部 |
--subsystem S |
子系统名包含 S |
--match S |
消息必须包含 S |
--exclude S |
消息不能包含 S |
--levels 里的 default 对应 Logger 的 notice 和 log,没有单独的 notice 写法。接了多台手机时,在命令前面加全局参数 --udid 指定设备。
按 App 过滤
日志用了 Bundle ID 做 subsystem 时,--subsystem 最直接。只想看错误时加上级别。
1kxdevice ostrace --subsystem com.example.myapp --levels error,fault
第三方库和 NSLog 的日志通常不带你的 subsystem,要连它们一起看,就按进程过滤。先用 kxdevice 启动 App,它会打印 pid,App 已在运行时用 apps ps 查。
1kxdevice apps launch com.example.myapp
2kxdevice apps ps --apps
3kxdevice ostrace --pid 1234
--subsystem 是「包含」匹配,填 com.example 会同时匹配 App 和它的扩展,例如小组件。
按分类和关键字过滤
ostrace 没有单独的分类参数。每行输出里都带着「子系统/分类」,用 findstr 或 grep 按分类筛。
1kxdevice ostrace --subsystem com.example.myapp | findstr "com.example.myapp/network"
1kxdevice ostrace --subsystem com.example.myapp | grep "com.example.myapp/network"
--match 和 --exclude 只看消息文字,适合按业务关键字筛,或者去掉刷屏的心跳日志。
1kxdevice ostrace --subsystem com.example.myapp --match 订单 --exclude heartbeat
syslog:整机系统日志
要看系统服务怎么处理你的 App,例如安装失败、权限被拒、App 被系统结束,用 kxdevice syslog。它输出整机日志,量很大,一定要配合过滤。
1kxdevice syslog | findstr MyApp
导出到文件
ostrace 和 syslog 都是实时输出,不会回放已经过去的日志。所以顺序是先开始记录,再复现问题。
在 PowerShell 里把输出存成 UTF-8 文件,中文不会乱码。
1kxdevice ostrace --subsystem com.example.myapp | Out-File -Encoding utf8 .\myapp-log.txt
在 Linux 或 WSL 里用 tee,屏幕上照常显示,同时写进文件。
1kxdevice ostrace --subsystem com.example.myapp | tee myapp-log.txt
复现完按 Ctrl-C 停止,文件里就是这段时间的日志。发给同事或附到问题单时,说明手机型号、iOS 版本和复现步骤。
不想记命令时,执行 kxdevice web,用浏览器打开 http://localhost:8780,系统日志、崩溃报告、进程列表都能在网页上查看。
怎么结合崩溃报告分析日志?
App 崩溃后,崩溃报告告诉你崩在哪一行,日志告诉你崩溃之前发生了什么。两者对着看,比只看调用栈快得多。
- 开一个终端,把 ostrace 导出到文件。
- 在手机上复现崩溃。
- 停止记录,拉取崩溃报告。
1kxdevice crash list MyApp*
2kxdevice crash pull .\crashes MyApp*
- 打开 .ips 文件,记下崩溃时间,在日志文件里找这个时间点之前的最后几条。
- 看最后一条正常日志之后、崩溃之前做了什么操作,缩小可疑代码的范围。
为了让这一步有用,在关键流程的入口和出口各打一条 notice,异常分支打 error。崩溃报告的读法见 Windows 上真机调试 iOS App的崩溃一节。
Flutter 和 React Native 工程的日志在哪看?
跨平台工程里有两层日志,分别在不同的地方看。
| 日志来源 | 在哪看 |
|---|---|
| ios 目录下 Swift、Objective-C 代码的 print、NSLog、Logger | 调试时在「输出 → KXBuild Console」,不调试时用 kxdevice ostrace |
| Flutter 的 Dart 日志(print、debugPrint、dart:developer 的 log) | 按 Flutter 官方说明,在 DevTools 的 Logging 视图里看 |
| React Native 的 JS 日志(console.log 等) | React Native DevTools 的 Console 面板,在运行 Metro 的终端里按 j 打开 |
- Flutter。原生插件、MethodChannel 处理函数里的日志用 Logger 写,和纯原生 App 一样过滤。Dart 一侧用
developer.log时,name参数起的作用类似 category,方便在 DevTools 里分类,见 Flutter 文档的 Add logging to your application。调试步骤见 Flutter 真机调试。 - React Native。原生模块的日志在 KXBuild Console,JS 日志和 JS 断点用 React Native 自带的调试工具,见 React Native 开发 iOS App。
日志级别与用途对照
写日志前对一下这张表,选好级别,查的时候 --levels 就能一次筛准。
| 场景 | Logger 方法 | ostrace --levels 写法 |
|---|---|---|
| 打印变量、循环里的中间值,只在开发时看 | debug |
debug |
| 记录流程进展,例如开始请求、读取缓存 | info |
info |
| 关键节点,例如登录成功、支付回调、首屏显示 | notice |
default |
| 可恢复的错误,例如请求失败、解析失败 | error |
error |
| 不该发生的状态,例如数据不一致、断言之外的异常分支 | fault |
fault |
| 只看出问题的部分 | — | error,fault |
按这张表定好级别,iOS 真机日志在 KXBuild Console 和 ostrace 里都能一次筛到要看的那几条。
常见问题
iPhone 上 App 没在调试时怎么看日志?
用 kxdevice ostrace 实时读取手机上的 os_log,加 –subsystem 填你的 Bundle ID 就只剩自己 App 的日志。代码里要用 Logger、os_log 或 NSLog,print 的输出不会出现在这里。
Logger 打的字符串为什么显示不出来?
动态字符串默认被系统当作隐私数据隐去。确定不敏感的值在插值里加 privacy: .public,例如 (screen, privacy: .public)。
Logger 的 debug 和 info 日志为什么过后找不到?
按 Apple 文档,debug 级别不写入磁盘,info 只有用 log 工具收集时才写入。要事后还能查到的日志用 notice 或更高级别,并且在复现前就开始用 ostrace 记录。
kxdevice ostrace 能按 category 过滤吗?
没有单独的分类参数。每行输出都带有子系统和分类名,用 –subsystem 先筛出自己的 App,再用 findstr 或 grep 按「子系统/分类」筛选。
怎么把 iPhone 日志导出成文件发给别人?
先开始记录再复现问题。PowerShell 里用 kxdevice ostrace –subsystem 你的BundleID | Out-File -Encoding utf8 .\log.txt,Linux 里用 tee,复现完按 Ctrl-C 停止。