preview() 出镜前过 trace.RedactTextLimited(600 rune),覆盖失败日志的 chrome_output 与 withChromeOutput 追加给调用方的错误串;补凭据形状/原话可见/有界三条测试,删实现可打红。
125 lines
6.9 KiB
Go
125 lines
6.9 KiB
Go
package browser
|
||
|
||
// 本文件是**对端自由文本出口**的收敛点:有三处会把页面/浏览器自己说的话原样交给我们,
|
||
// 它们出镜(进 error ⇒ 日志 + 模型可见视图)之前必须在这里过一道。
|
||
//
|
||
// 三处**不是同一类**,因此守卫也不同(口径见 internal/runtime/trace/errortoken.go
|
||
// 的「按字段类别判定」):
|
||
//
|
||
// - `Page.navigate` 的 `errorText` 是**协议枚举**:值域是 Chrome 的 net error 码
|
||
// (`net::ERR_NAME_NOT_RESOLVED` 之类),短、标识符形状 ⇒ 用 trace.ErrorToken 守:
|
||
// 像枚举就保留,散文一律丢弃;
|
||
// - `Runtime.evaluate` 的 `exceptionDetails.text` 是**页面自由文本**:页面的 JS 可以
|
||
// throw 任何东西 —— 包括它读到的 DOM 正文、地址栏里带 token 的 URL。⇒ 默认不带
|
||
// 全文,按「带上限地带」处理:压平空白 + trace.RedactTextLimited 截断;
|
||
// - 浏览器自己的 stdout/stderr 预览(launchoutput.go 的 `preview()`)也是自由文本:
|
||
// 参数被拒时 Chromium 会把**本次 argv** 与地址原话打出来(`chromeOutputPreview`)。
|
||
//
|
||
// 🔴 两类守卫**不能互换**:拿 ErrorToken 去「清洗」自由文本是错的口径(它只会把整段
|
||
// 话删掉,不是清洗);拿 RedactTextLimited 去守枚举字段也不对(它不判形状,挡不住
|
||
// 散文)。清洗实现仍然只有一份,本文件只做「选哪一道 + 补形状前提」。
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/trace"
|
||
)
|
||
|
||
// maxExceptionTextRunes 是 `exceptionDetails.text` 允许出镜的 rune 上限:**200**。
|
||
//
|
||
// 为什么不复用既有的两个档位:
|
||
// - **80**(`trace.RedactText` 的摘要档)会把真实的异常消息拦腰截断 ——
|
||
// `TypeError: Cannot read properties of undefined (reading 'addEventListener')`
|
||
// 已经 73 rune,再带上半句调用点就过 80,而调用点是唯一的定位线索;
|
||
// - **4096**(网关日志页 `maxLogMsgRunes` 那一档)是给「运维要读全文」的日志行用的;
|
||
// 这个字段是**页面自己说的话**,4096 等于允许页面把 4KB 正文塞进一条错误串 ——
|
||
// 而这条错误串同时进日志与模型可见视图。
|
||
//
|
||
// 200 容得下「异常消息 + 一段调用点」,又把页面可控文本压在「一句话」量级。超限时
|
||
// trace.RedactTextLimited 会补 `…`,因此截断本身是看得见的,不会被误读成全文。
|
||
const maxExceptionTextRunes = 200
|
||
|
||
// netErrorNamespace 是 Chrome net error 码的命名空间前缀(`net::ERR_...`)。
|
||
//
|
||
// 为什么要先剥掉它再交给 ErrorToken:ErrorToken 的字符集是 `[A-Za-z0-9_.-]`,**不含
|
||
// `:`**,整串直接送进去会把合法的网络错误码一起判掉 —— 而「合法网络错误码形态保留」
|
||
// 是这条出口的硬要求(上层与工具都在断言它,见 render_test ③)。这个前缀是**协议
|
||
// 常量**,不是从对端抄来的文本,因此剥它是安全的。
|
||
const netErrorNamespace = "net::"
|
||
|
||
// netErrorCodePrefix 是 net error 枚举的名字前缀(`ERR_`):所有真实取值都以它开头。
|
||
// 于是这一条额外把「恰好是单个令牌的自由文本」(`hunter2` 之类)也挡掉 —— 形状判定
|
||
// 仍由 trace.ErrorToken 做,本文件只补这一个前提,不复制第二份守卫实现。
|
||
const netErrorCodePrefix = "ERR_"
|
||
|
||
// netErrorCode 只放行「像 net error 枚举」的 errorText:`[net::]ERR_<标识符>`。
|
||
// 其余(散文、被回显的 URL、带凭据的文本)返回空串,由调用方按「已隐去」处理。
|
||
func netErrorCode(raw string) string {
|
||
text := strings.TrimSpace(raw)
|
||
namespace := ""
|
||
if rest, ok := strings.CutPrefix(text, netErrorNamespace); ok {
|
||
namespace, text = netErrorNamespace, rest
|
||
}
|
||
if !strings.HasPrefix(text, netErrorCodePrefix) {
|
||
return ""
|
||
}
|
||
token := trace.ErrorToken(text)
|
||
if token == "" {
|
||
return ""
|
||
}
|
||
return namespace + token
|
||
}
|
||
|
||
// navigateCause 把 `Page.navigate` 的 errorText 变成可出镜的原因子句。
|
||
// 调用前提:raw 非空(navigate 只在 errorText 非空时判定为失败)。
|
||
//
|
||
// 不是枚举时一个字符的自由文本都不带,只留**字节数** —— 够判断「对端说了句话」,
|
||
// 不带那句话(与 cdpError.Error 同一口径)。
|
||
func navigateCause(raw string) string {
|
||
if code := netErrorCode(raw); code != "" {
|
||
return code
|
||
}
|
||
return fmt.Sprintf("网络错误文本已隐去(%d 字节)", len(raw))
|
||
}
|
||
|
||
// pageExceptionText 是 `exceptionDetails.text` 出镜前的唯一处理:压平空白(一个换行
|
||
// 就能在按行聚合的日志里伪造出「另一条记录」,与 sdklog / judgeReason 同一做法)+
|
||
// 凭据形状打码 + 截断(清洗实现只有一份:trace.RedactTextLimited)。
|
||
func pageExceptionText(raw string) string {
|
||
return trace.RedactTextLimited(strings.Join(strings.Fields(raw), " "), maxExceptionTextRunes)
|
||
}
|
||
|
||
// evaluateCause 把 `Runtime.evaluate` 的 exceptionDetails.text 变成可出镜的原因子句。
|
||
func evaluateCause(raw string) string {
|
||
if text := pageExceptionText(raw); text != "" {
|
||
return text
|
||
}
|
||
// 压平之后什么都不剩(对端只给了空白):如实说「没给文本」,不假装异常无因。
|
||
return "页面异常(对端未给文本)"
|
||
}
|
||
|
||
// chromeOutputPreviewRunes 是浏览器 stdout/stderr 预览出镜时的 rune 上限:**600**
|
||
// (= 12 行 × 50 rune)。
|
||
//
|
||
// 为什么是这个量级:预览的**行**预算已经是 12 行(chromeOutputMaxLines),50 rune 大致
|
||
// 是一个短分句 —— Chromium 的抱怨句式是「原因在前」(`ERROR:...] 参数被拒:...`),
|
||
// 所以 600 保得住原因那一句,又把「一整块转储」挡在模型可见的错误串之外。
|
||
//
|
||
// 为什么不沿用另外两档:80(`trace.RedactText` 摘要档)连**一行**浏览器日志都放不下;
|
||
// 4096(`chromeOutputMaxBytes` 缓存档、网关日志页 maxLogMsgRunes)是「运维读全文」的
|
||
// 量级,对浏览器可控的字段等于允许把 4KB 正文塞进工具结果。
|
||
//
|
||
// 为什么 600 不会把「到底说了多少」藏起来:本上限只压在**预览正文**上,调用方
|
||
// (launchoutput.go)随后仍会追加「已截断,共 N 字节」——读者看到的永远是「这里有 600
|
||
// rune 的原话 + 它其实说了 N 字节」,截断与量级都看得见。
|
||
const chromeOutputPreviewRunes = 600
|
||
|
||
// chromeOutputPreview 是浏览器 stdout/stderr 预览出镜前的唯一处理:凭据形状打码 +
|
||
// 截断(清洗实现只有一份:trace.RedactTextLimited)。它服务**两个**读面 —— 失败日志的
|
||
// `chrome_output` 字段与返回给调用方的错误串(launch.go 的 launchOnce),因此收敛在
|
||
// preview() 里,两个读面不会判得不一致。
|
||
func chromeOutputPreview(text string) string {
|
||
return trace.RedactTextLimited(text, chromeOutputPreviewRunes)
|
||
}
|