日志: 远程只收「启动检测 + 报错」,info/notice/warn 一律只写本地

- MiliLog 新增 remote()(启动检测专用,仍上远程);info/notice/warn 改为只写本地
- 启动检测 4 处改用 remote(): APP-INIT / BOOT / STORAGE / JS-BRIDGE ready
- error 语义不变(报错仍上远程);local* 不变(只写本地)
- 调用点名称不变,其余 40+ 处只是不再上传
- AGENTS 第 5 条日志约定重写;版本号 1.6.3(46) → 1.6.4(47)
- 验证: assembleRelease + 单测 127 例全过
This commit is contained in:
zijunchiang
2026-10-14 20:14:43 +08:00
parent c7f8224a73
commit a21f4d0b1e
6 changed files with 39 additions and 24 deletions
+16 -15
View File
@@ -195,12 +195,13 @@ $BT/apksigner verify 目标名.apk
### 版本号 ### 版本号
- `app/build.gradle`:`versionName "1.6.3"` / `versionCode 46` - `app/build.gradle`:`versionName "1.6.4"` / `versionCode 47`
(`1.5.5`/`37` 已发布;`1.5.6` = 更新源加 Gitee、`1.5.7` = 删除「歌曲源临时受限」弹窗、`1.5.8` = 精简远程日志、`1.5.9` = 按 HAR 对齐原版协议、`1.5.10` = 源列表只留 zk.ktv1.top:6677、`1.5.11`(命名越界,已改) / `1.6.0` = whoami 心跳/设备身份/点歌推送/带 query 的地址闸门 + 并入 selfhost-source 支线、 (`1.5.5`/`37` 已发布;`1.5.6` = 更新源加 Gitee、`1.5.7` = 删除「歌曲源临时受限」弹窗、`1.5.8` = 精简远程日志、`1.5.9` = 按 HAR 对齐原版协议、`1.5.10` = 源列表只留 zk.ktv1.top:6677、`1.5.11`(命名越界,已改) / `1.6.0` = whoami 心跳/设备身份/点歌推送/带 query 的地址闸门 + 并入 selfhost-source 支线、
`1.6.1`/`44` = App 接入自建源(自建源优先 / App 令牌 / 媒体允许清单 / 推送默认目标)、 `1.6.1`/`44` = App 接入自建源(自建源优先 / App 令牌 / 媒体允许清单 / 推送默认目标)、
`1.6.2`/`45` = **取址反转成"官方源优先、自建源兜底" + 只有官方源成功才上报**(含 6 条隐患修复与死代码清理)、 `1.6.2`/`45` = **取址反转成"官方源优先、自建源兜底" + 只有官方源成功才上报**(含 6 条隐患修复与死代码清理)、
`1.6.3`/`46` = **点歌延迟优化**(终态失败不退避 / 上游 i.php token 缓存 / 后台预热 / 分阶段耗时日志), `1.6.3`/`46` = **点歌延迟优化**(终态失败不退避 / 上游 i.php token 缓存 / 后台预热 / 分阶段耗时日志),
以上 1.5.6~1.6.3 均尚未发布。18~21 曾被本机临时诊断包占用) `1.6.4`/`47` = **远程日志只收「启动检测 + 报错」**(info/notice/warn 一律只写本地),
以上 1.5.6~1.6.4 均尚未发布。18~21 曾被本机临时诊断包占用)
- App 内显示统一用纯版本号(不加 `(versionCode)` 括号) - App 内显示统一用纯版本号(不加 `(versionCode)` 括号)
- ⚠️ **本机 `/tmp` 是 tmpfs,单测必须绕开它**:`/tmp`(5.6G tmpfs)长期被"已删除但仍被打开"的 - ⚠️ **本机 `/tmp` 是 tmpfs,单测必须绕开它**:`/tmp`(5.6G tmpfs)长期被"已删除但仍被打开"的
文件占满,单测写临时文件会抛 `IOException: 超出磁盘配额`(EDQUOT),**看起来像测试失败**。 文件占满,单测写临时文件会抛 `IOException: 超出磁盘配额`(EDQUOT),**看起来像测试失败**。
@@ -516,8 +517,10 @@ $BT/apksigner verify 目标名.apk
由 `MiliLog.PUBLIC_IP_URL` 查询,结果缓存 5 分钟、失败 1 分钟后重试,查询在独立后台线程, 由 `MiliLog.PUBLIC_IP_URL` 查询,结果缓存 5 分钟、失败 1 分钟后重试,查询在独立后台线程,
绝不在主线程发起),`clientip` = **客户端 IP**(设备当前网卡获取到的局域网 IP,由 绝不在主线程发起),`clientip` = **客户端 IP**(设备当前网卡获取到的局域网 IP,由
`MiliLog.updateDeviceContext(ip = ...)` 注入,网络变化时自动刷新并顺带复核公网 IP)。 `MiliLog.updateDeviceContext(ip = ...)` 注入,网络变化时自动刷新并顺带复核公网 IP)。
客户端为 `MiliLog`(有界队列 + 后台线程 + 失败重试),调用点 `MiliLog.info/notice/warn/error` 客户端为 `MiliLog`(有界队列 + 后台线程 + 失败重试)。⚠️ **级别语义见下面第 5 条**:
保持不变。启动上报(`reportDeviceBootInfo()`)只采集这 10 项:包名、版本号、安装时间、品牌、型号、 现在只有 `MiliLog.remote(...)`(启动检测)与 `MiliLog.error(...)`(报错)会上传,
`info/notice/warn` 已改为**只写本地**(调用点名可以不变,但不要再指望它们出现在远程服务上)。
启动上报(`reportDeviceBootInfo()`)只采集这 10 项:包名、版本号、安装时间、品牌、型号、
设备名、Android 版本、CPU 架构、内核版本、屏幕分辨率 设备名、Android 版本、CPU 架构、内核版本、屏幕分辨率
- **写日志的五条约定**(2026-09-26 依据现场日志复盘补充): - **写日志的五条约定**(2026-09-26 依据现场日志复盘补充):
1. **持久状态只记一次**,不要每次事件都记。反例:桥不可用曾按「每次点歌一条 WARN」输出, 1. **持久状态只记一次**,不要每次事件都记。反例:桥不可用曾按「每次点歌一条 WARN」输出,
@@ -531,17 +534,15 @@ $BT/apksigner verify 目标名.apk
4. **日志要能自我定位**:失败行写清"哪一步/哪个源/耗时/原因",例如 4. **日志要能自我定位**:失败行写清"哪一步/哪个源/耗时/原因",例如
`NATIVE-SONG 全部源失败 编号=... 详情=token-fail|no-url|demo-or-mismatch 用时=...ms`; `NATIVE-SONG 全部源失败 编号=... 详情=token-fail|no-url|demo-or-mismatch 用时=...ms`;
桥侧由 `JS-BRIDGE ...` 说明 WebView 到底卡在哪一步(初始化失败/KtvApi 未导出/20s 超时)。 桥侧由 `JS-BRIDGE ...` 说明 WebView 到底卡在哪一步(初始化失败/KtvApi 未导出/20s 超时)。
5. **⚠️ 远程日志只留"每次事件一条"的汇总,细节走本地(2026-10 精简)**: 5. **⚠️⚠️ 远程日志只收「启动检测 + 报错」两类(2026-10-15 二次收紧,用户要求)**:
`MiliLog.local(msg)` / `localWarn` / `localError` **只写本地文件、不上报远程**。 `MiliLog` 的级别语义已定死为 ——
判定标准——**逐源 / 逐次重试 / 单次 HTTP 错误 / JS 控制台输出**一律用 `local*`; - **上远程**:`MiliLog.remote(...)`(**仅启动检测**:`APP-INIT` / `BOOT` / `STORAGE` /
远程只保留"这件事的结论": `JS-BRIDGE ready`)+ `MiliLog.error(...)`(**报错**);
- 取址:成功一条 `NATIVE-SONG 取址成功 编号=… 源=… urlHost=… 用时=…ms`, - **只写本地**:`info` / `notice` / `warn` / `local` / `localWarn` / `localError`
失败一条 `NATIVE-SONG 全部源失败 编号=… 详情=… 用时=…ms`, → `<存储根>/logs/ktv.log`(纯文本,什么都不缺)。
全部只发广告一条 `SOURCE-BLOCKED 全部 N 个源只返回广告/测试资源…` ⚠️ **不要**把 `info`/`notice`/`warn` 改回上传;想让某条"警告类失败"也进远程,
→ **每次取址远程恰好一条**(改前是每个源一条,9 个源就是 9+ 条); 就把它**显式改成 `MiliLog.error(...)`**(一次改动、意图可见)。
- `JS …`(ktv_api.js 的 console 转发)**只进本地**,不再进远程; 现场排查一律先取本地日志文件;远程只回答"机器起来了没"与"有没有报错"。
JS 路径失败仍由远程的 `点歌失败 编号=… 请求源=… 详情=…` 一行说明原因。
⚠️ 新加日志时先问自己:"这条是'结论'还是'过程'?" —— 过程写本地。
6. **状态样本 + 事件**缺一不可:事件(点歌/失败)说明"发生了什么",但排查"随时间变化"的 6. **状态样本 + 事件**缺一不可:事件(点歌/失败)说明"发生了什么",但排查"随时间变化"的
问题(空间变少、曲库丢失、长期离线)需要状态样本 —— 现由 6 小时一条 `HEALTH ...` 问题(空间变少、曲库丢失、长期离线)需要状态样本 —— 现由 6 小时一条 `HEALTH ...`
(版本/在线/可用空间/曲库大小/根目录)提供。 (版本/在线/可用空间/曲库大小/根目录)提供。
+2 -2
View File
@@ -20,8 +20,8 @@ android {
applicationId "com.local.ktv" applicationId "com.local.ktv"
minSdk 21 minSdk 21
targetSdk 25 targetSdk 25
versionCode 46 versionCode 47
versionName "1.6.3" versionName "1.6.4"
ndk { ndk {
// 双 ABI 打包:兼容 64 位(Exynos 1580 等 arm64-v8a)与 32 位设备(极米 Play 超越版 // 双 ABI 打包:兼容 64 位(Exynos 1580 等 arm64-v8a)与 32 位设备(极米 Play 超越版
// 的 Amlogic 950X2/S905X2 采用 32 位 Android 用户态,必须包含 armeabi-v7a,否则 // 的 Amlogic 950X2/S905X2 采用 32 位 Android 用户态,必须包含 armeabi-v7a,否则
@@ -31,7 +31,7 @@ class KtvApplication : Application() {
// init 现在可重复调用且会自动重启发送线程,这里是"最靠前"的启动点。 // init 现在可重复调用且会自动重启发送线程,这里是"最靠前"的启动点。
runCatching { runCatching {
MiliLog.init(this) MiliLog.init(this)
MiliLog.info("APP-INIT 进程启动 ver=${BuildConfig.VERSION_NAME} logFile=${MiliLog.localLogPath()}") MiliLog.remote("APP-INIT 进程启动 ver=${BuildConfig.VERSION_NAME} logFile=${MiliLog.localLogPath()}")
}.onFailure { Log.e(TAG, "MiliLog.init failed", it) } }.onFailure { Log.e(TAG, "MiliLog.init failed", it) }
OkDownload.setSingletonInstance(OkDownload.Builder(this).build()) OkDownload.setSingletonInstance(OkDownload.Builder(this).build())
Log.i(TAG, "KTV application initialized without native services") Log.i(TAG, "KTV application initialized without native services")
@@ -287,7 +287,7 @@ class KtvJsBridge(context: Context) {
unavailableReason = "" unavailableReason = ""
} }
Log.i(TAG, "JS runtime ready (${if (loadedFromCache) "cache" else "asset"})") Log.i(TAG, "JS runtime ready (${if (loadedFromCache) "cache" else "asset"})")
MiliLog.info("JS-BRIDGE ready (${if (loadedFromCache) "cache" else "asset"})") MiliLog.remote("JS-BRIDGE ready (${if (loadedFromCache) "cache" else "asset"})")
callback(true) callback(true)
} else if (loadedFromCache && !assetRetryAttempted) { } else if (loadedFromCache && !assetRetryAttempted) {
Log.w(TAG, "Cached JS failed to load; falling back to packaged asset") Log.w(TAG, "Cached JS failed to load; falling back to packaged asset")
@@ -3878,10 +3878,10 @@ class MainActivity : AppCompatActivity() {
sb.append(" screen=").append(screenResolutionText()) sb.append(" screen=").append(screenResolutionText())
// 等主机 IP(公网出口 IP)就绪,避免启动这条日志的 serverip 为空。 // 等主机 IP(公网出口 IP)就绪,避免启动这条日志的 serverip 为空。
MiliLog.awaitServerIp() MiliLog.awaitServerIp()
MiliLog.notice(sb.toString()) MiliLog.remote(sb.toString()) // BOOT 启动检测行
// 存储根目录探测结果(排查不同盒子存储差异用;单行便于按 STORAGE 检索)。 // 存储根目录探测结果(排查不同盒子存储差异用;单行便于按 STORAGE 检索)。
// 放在这里而不是启动主线程:既有完整上下文,又避免了主线程磁盘探测。 // 放在这里而不是启动主线程:既有完整上下文,又避免了主线程磁盘探测。
runCatching { MiliLog.notice("STORAGE ${AppPaths.storageFacts(this@MainActivity)}") } runCatching { MiliLog.remote("STORAGE ${AppPaths.storageFacts(this@MainActivity)}") }
} catch (ignored: Exception) { } catch (ignored: Exception) {
} }
}, "ktv-boot-log").start() }, "ktv-boot-log").start()
+17 -3
View File
@@ -153,11 +153,25 @@ object MiliLog {
} }
} }
fun info(msg: String) = enqueue("INFO", msg) /**
fun notice(msg: String) = enqueue("INFO", msg) * ⚠️ **远程日志策略(2026-10-15 收紧,用户要求)**:远程服务**只收两类** ——
fun warn(msg: String) = enqueue("WARN", msg) * 1. **启动检测**:显式用 [remote](`APP-INIT` / `BOOT` / `STORAGE` / `JS-BRIDGE ready` 等一次性行);
* 2. **报错**:[error]。
*
* 其余一切(info / notice / warn、逐源逐次细节、JS 控制台输出、6 小时健康样本……)
* **只写本地** `<存储根>/logs/ktv.log`,不再上报远程。
*
* ⚠️ 不要顺手把 [info]/[notice]/[warn] 改回上传:远程只做"这台机器是否起来 + 有没有报错",
* 需要细节时让现场把本地日志文件发出来(那里面什么都不缺)。
*/
fun info(msg: String) = enqueueLocal("INFO", msg)
fun notice(msg: String) = enqueueLocal("INFO", msg)
fun warn(msg: String) = enqueueLocal("WARN", msg)
fun error(msg: String) = enqueue("ERROR", msg) fun error(msg: String) = enqueue("ERROR", msg)
/** **启动检测**专用:这类一次性行仍上报远程(其余细节只写本地)。 */
fun remote(msg: String) = enqueue("INFO", msg)
/** /**
* **只写本地**的详细日志(2026-10 新增)。 * **只写本地**的详细日志(2026-10 新增)。
* *