140 lines
4.7 KiB
Markdown
140 lines
4.7 KiB
Markdown
|
|
---
|
|||
|
|
name: Fix caller detection
|
|||
|
|
overview: 修复三个日志/测试相关 bug:(1) 调用者智能识别漏掉 log/ 包自身 (2) 测试模式 SQL 出错不会导致用例失败 (3) 测试模式 WebConnectLog 写入已关闭的 NUL 文件
|
|||
|
|
todos:
|
|||
|
|
- id: fix-framework-detection
|
|||
|
|
content: 在 isHoTimeFrameworkFile 中 runtime/ 检查之后,添加 log/ 前缀检查
|
|||
|
|
status: completed
|
|||
|
|
- id: fix-sql-error-test-fail
|
|||
|
|
content: 测试模式 SQL 出错时自动让当前用例失败(db 增加错误计数 + execute 中检查)
|
|||
|
|
status: completed
|
|||
|
|
- id: fix-zerolog-nul-closed
|
|||
|
|
content: NewTestApp 中 Init 完成后将 WebConnectLog 置为 nil,避免写入已关闭的 NUL 文件
|
|||
|
|
status: completed
|
|||
|
|
isProject: false
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# 修复日志/测试三个 Bug
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Bug 1:调用者智能识别始终显示 log/logger.go:318
|
|||
|
|
|
|||
|
|
### 根因
|
|||
|
|
|
|||
|
|
[log/logger.go](log/logger.go) 中的 `isHoTimeFrameworkFile` 函数对 `log/` 包自身存在识别盲区。
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
A["应用代码 或 db/query.go 调用 Logger.Debug()"] --> B["zerolog Event.Msg()"]
|
|||
|
|
B --> C["callerMarshalFunc (log/logger.go:317)"]
|
|||
|
|
C --> D["findCaller 通过 runtime.Caller(i) 逐帧遍历调用栈"]
|
|||
|
|
D --> E{"isHoTimeFrameworkFile?"}
|
|||
|
|
E -->|"是框架文件 -> 跳过,继续向上"| D
|
|||
|
|
E -->|"非框架文件 -> 作为调用者返回"| F["显示在日志输出中"]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`findCaller` 在 `i=1` 时拿到 `log/logger.go:318`。经过 `shortenPath` 截断后变成 `log/logger.go`。两条检测路径都漏掉了它:
|
|||
|
|
|
|||
|
|
- **路径一(第 392 行)**:`frameworkDirs` 包含 `"log/"`,但要求路径含 `"hotime"`。`shortenPath` 只保留 2 段路径,`log/logger.go` 中没有 `"hotime"`。**未命中。**
|
|||
|
|
- **路径二(第 410 行)**:`frameworkCoreDirs` 只有 `db/`、`common/`、`code/`、`cache/`,**没有 `"log/"`**。**未命中。**
|
|||
|
|
|
|||
|
|
### 修复
|
|||
|
|
|
|||
|
|
在 [log/logger.go](log/logger.go) 第 387-389 行 `runtime/` 检查之后加入 `log/` 前缀检查:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
if strings.HasPrefix(file, "runtime/") {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
if strings.HasPrefix(file, "log/") {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Bug 2:测试模式 SQL 执行失败不会导致用例失败
|
|||
|
|
|
|||
|
|
### 根因
|
|||
|
|
|
|||
|
|
[db/query.go](db/query.go) `logSQL` 在测试模式下只是把 SQL 错误写入 `testLogBuf`,但 [testing_api.go](testing_api.go) 的 `execute` 中只在断言失败时才打印这些日志,**从不检查是否有 SQL 错误**。即使请求处理过程中数据库出错,只要 HTTP 响应的 status/msg 符合预期,测试就通过了。
|
|||
|
|
|
|||
|
|
当前流程:
|
|||
|
|
```mermaid
|
|||
|
|
flowchart LR
|
|||
|
|
A["ServeHTTP"] --> B["检查 status/msg/result"]
|
|||
|
|
B -->|"通过"| C["FlushTestLog 丢弃"]
|
|||
|
|
B -->|"失败"| D["FlushTestLog 打印"]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 修复
|
|||
|
|
|
|||
|
|
在 [db/db.go](db/db.go) 增加一个测试 SQL 错误计数器:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
testSQLErrCount int32 // 测试模式下 SQL 错误计数(atomic)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
在 [db/query.go](db/query.go) `logSQL` 中,当 `err != nil` 且 `testLogBuf != nil` 时,`atomic.AddInt32(&that.testSQLErrCount, 1)`。
|
|||
|
|
|
|||
|
|
在 [db/db.go](db/db.go) 增加两个方法:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
func (that *HoTimeDB) TestSQLErrorCount() int32 {
|
|||
|
|
return atomic.LoadInt32(&that.testSQLErrCount)
|
|||
|
|
}
|
|||
|
|
func (that *HoTimeDB) ResetTestSQLErrorCount() {
|
|||
|
|
atomic.StoreInt32(&that.testSQLErrCount, 0)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
在 [testing_api.go](testing_api.go) `execute` 中,`ServeHTTP` 之后、断言之前,检查计数:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
c.api.app.Db.FlushTestLog() // 先清空请求前的残留
|
|||
|
|
// ... ServeHTTP ...
|
|||
|
|
sqlErrCount := c.api.app.Db.TestSQLErrorCount()
|
|||
|
|
c.api.app.Db.ResetTestSQLErrorCount()
|
|||
|
|
if sqlErrCount > 0 {
|
|||
|
|
sqlLog := c.api.app.Db.FlushTestLog()
|
|||
|
|
t.Fatalf("请求处理中有 %d 次 SQL 错误:\n%s", sqlErrCount, sqlLog)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
修复后流程:
|
|||
|
|
```mermaid
|
|||
|
|
flowchart LR
|
|||
|
|
A["ServeHTTP"] --> B{"SQL 出错?"}
|
|||
|
|
B -->|"是"| C["t.Fatalf 用例直接失败"]
|
|||
|
|
B -->|"否"| D["正常断言 status/msg/result"]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Bug 3:zerolog: could not write event: write NUL: file already closed
|
|||
|
|
|
|||
|
|
### 根因
|
|||
|
|
|
|||
|
|
[testing_helper.go](testing_helper.go) `NewTestApp` 中的初始化顺序:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. os.Stderr = devNull (重定向到 NUL)
|
|||
|
|
2. Init(configPath) (创建 WebConnectLog,其 ConsoleWriter.Out = devNull)
|
|||
|
|
3. os.Stderr = origStderr (恢复真实 stderr)
|
|||
|
|
4. devNull.Close() (关闭 NUL 文件)
|
|||
|
|
5. app.Log.SetOutput(io.Discard) (只修复了主日志)
|
|||
|
|
-- WebConnectLog 仍然引用已关闭的 devNull --
|
|||
|
|
6. 测试请求触发 WebConnectLog.Info() → 写入已关闭文件 → 报错
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 修复
|
|||
|
|
|
|||
|
|
在 [testing_helper.go](testing_helper.go) `devNull.Close()` 之后、`app.Log.SetOutput(io.Discard)` 之前,加一行:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
app.WebConnectLog = nil
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
测试环境不需要访问日志。`application.go` 第 350 行 `if that.WebConnectLog != nil` 已有 nil 保护,置 nil 即可安全跳过。
|