refactor(logging): 迁移日志记录到 Zerolog 并优化错误处理
- 将日志记录库从 Logrus 替换为 Zerolog,提升性能和灵活性 - 更新各个模块的日志记录方式,确保一致性 - 优化错误处理逻辑,确保在发生错误时能够正确记录并传递错误信息 - 移除不再使用的错误处理字段,简化代码结构 - 更新相关文档以反映新的日志记录和错误处理机制
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
---
|
||||
name: DBState refactor to lastError
|
||||
overview: 升级 common.Error 基础类型(安全 Error() + HasError() + Unwrap()),删除 common/dbstate.go,新增 db/dberror.go(DBError 嵌入 Error),用 atomic.Pointer 无锁读写。
|
||||
todos:
|
||||
- id: upgrade-error
|
||||
content: 升级 common/error.go:添加安全 Error()、HasError()、Unwrap() 方法,更新注释
|
||||
status: completed
|
||||
- id: delete-dbstate
|
||||
content: 删除 common/dbstate.go
|
||||
status: completed
|
||||
- id: add-dberror-type
|
||||
content: 新增 db/dberror.go(DBError 嵌入 Error),db/db.go 添加 atomic.Pointer[DBError] + GetLastError()
|
||||
status: completed
|
||||
- id: update-db-internals
|
||||
content: 更新 db/query.go、transaction.go、crud.go、db.go 中所有 lastState 调用
|
||||
status: completed
|
||||
- id: update-examples
|
||||
content: 更新 example/app/test.go 和 mysql.go
|
||||
status: completed
|
||||
- id: build-and-test
|
||||
content: 编译 + 运行测试验证
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Error 升级 + DBState 重构
|
||||
|
||||
## 一、升级 common.Error 基础类型
|
||||
|
||||
当前 [common/error.go](common/error.go) 只有 `GetError()`/`SetError()`,存在问题:
|
||||
|
||||
- `Error()` 方法由嵌入的 `error` 接口提供,**error 为 nil 时调用会 panic**
|
||||
- 缺少 `errors.Is`/`errors.As` 支持(Go 标准错误链)
|
||||
- 判断有无错误要写 `e.GetError() != nil`,不够简洁
|
||||
|
||||
升级后(**向后兼容,纯增量**):
|
||||
|
||||
```go
|
||||
type Error struct {
|
||||
error
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// Error 安全实现 error 接口 —— nil 时返回 "" 而非 panic
|
||||
func (that *Error) Error() string {
|
||||
that.mu.RLock()
|
||||
defer that.mu.RUnlock()
|
||||
if that.error == nil {
|
||||
return ""
|
||||
}
|
||||
return that.error.Error()
|
||||
}
|
||||
|
||||
// HasError 快捷判断(替代 GetError() != nil)
|
||||
func (that *Error) HasError() bool {
|
||||
that.mu.RLock()
|
||||
defer that.mu.RUnlock()
|
||||
return that.error != nil
|
||||
}
|
||||
|
||||
// Unwrap 支持 errors.Is / errors.As 标准错误链
|
||||
func (that *Error) Unwrap() error {
|
||||
that.mu.RLock()
|
||||
defer that.mu.RUnlock()
|
||||
return that.error
|
||||
}
|
||||
|
||||
// GetError / SetError 保持不变
|
||||
```
|
||||
|
||||
## 二、删除 common/dbstate.go
|
||||
|
||||
删除 `DBState` 和 `DBSnapshot` 类型,DB 专用类型不该放 common。
|
||||
|
||||
## 三、新增 db/dberror.go —— 嵌入 Error,框架统一
|
||||
|
||||
```go
|
||||
package db
|
||||
|
||||
import . "code.hoteas.com/golang/hotime/common"
|
||||
|
||||
// DBError 最后一次 SQL 操作的结构化快照
|
||||
// 嵌入框架 Error 类型,与 Obj 等保持一致的错误 API
|
||||
type DBError struct {
|
||||
Error // 嵌入 common.Error:HasError()/GetError()/Unwrap() 直接可用
|
||||
Query string
|
||||
Data []interface{}
|
||||
}
|
||||
```
|
||||
|
||||
应用层使用体验与 Obj 一致:
|
||||
|
||||
```go
|
||||
// 判断出错(与 Obj 同样的 API)
|
||||
if db.GetLastError().HasError() { /* SQL 出错 */ }
|
||||
|
||||
// 取原始 error
|
||||
rawErr := db.GetLastError().GetError()
|
||||
|
||||
// Go 标准错误链判断
|
||||
if errors.Is(db.GetLastError(), sql.ErrNoRows) { ... }
|
||||
|
||||
// 结构化字段(调试/SQL 监测系统)
|
||||
e := db.GetLastError()
|
||||
fmt.Println(e.Query, e.Data)
|
||||
```
|
||||
|
||||
## 四、HoTimeDB:atomic.Pointer 无锁读写
|
||||
|
||||
```go
|
||||
type HoTimeDB struct {
|
||||
// ...
|
||||
lastError atomic.Pointer[DBError]
|
||||
}
|
||||
|
||||
func (db *HoTimeDB) GetLastError() *DBError {
|
||||
return db.lastError.Load()
|
||||
}
|
||||
```
|
||||
|
||||
## 五、内部写入:替换所有 lastState 调用
|
||||
|
||||
每次 SQL 执行后创建新 DBError 并原子 Store:
|
||||
|
||||
```go
|
||||
e := &DBError{Query: query, Data: args}
|
||||
e.SetError(sqlErr) // 用框架 Error 的 SetError
|
||||
that.lastError.Store(e)
|
||||
```
|
||||
|
||||
具体替换点:
|
||||
|
||||
- [db/query.go](db/query.go):2 处 `lastState.Set(query, args, sqlErr)` + 2 处 `lastState.SetError(err)`
|
||||
- [db/db.go](db/db.go):InitDb 中 2 处 `lastState.SetError(e)`
|
||||
- [db/transaction.go](db/transaction.go):3 处 `lastState.SetError(err)`
|
||||
- [db/crud.go](db/crud.go):1 处 `lastState.SetError(e)`
|
||||
|
||||
## 六、外部调用适配
|
||||
|
||||
- [example/app/test.go](example/app/test.go):22 处 `GetLastState().Query` -> `GetLastError().Query`
|
||||
- [example/app/mysql.go](example/app/mysql.go):3 处,同上
|
||||
|
||||
始终记录(成功时 `.GetError() == nil`),`.Query` 可直接安全读取。
|
||||
|
||||
## 七、与 Logger 的分工
|
||||
|
||||
- Logger(`logSQL`):格式化输出(带日期、caller、级别)—— 人看的
|
||||
- `lastError`:结构化原始数据(Query/Data/原始 error)—— 程序读的
|
||||
- 零重复:原始 error 只存一份
|
||||
|
||||
## 改动文件清单
|
||||
|
||||
- `common/error.go` — 添加 `Error()`/`HasError()`/`Unwrap()` 方法,更新注释
|
||||
- `common/dbstate.go` — 删除
|
||||
- `db/dberror.go` — 新增:DBError struct(嵌入 Error)
|
||||
- `db/db.go` — `atomic.Pointer[DBError]` 字段 + `GetLastError()` 方法
|
||||
- `db/query.go` — `lastState.Set` -> `lastError.Store`
|
||||
- `db/transaction.go` — `lastState.SetError` -> `lastError.Store`
|
||||
- `db/crud.go` — 同上
|
||||
- `example/app/test.go` — `GetLastState()` -> `GetLastError()`
|
||||
- `example/app/mysql.go` — 同上
|
||||
@@ -0,0 +1,424 @@
|
||||
---
|
||||
name: Logging System Redesign
|
||||
overview: Replace logrus with zerolog, remove mode config, redesign logLevel/logFile, simplify Error to pure storage, add DBState for atomic DB state access, and provide a clean application-layer logging API.
|
||||
todos:
|
||||
- id: add-zerolog-dep
|
||||
content: 添加 zerolog 依赖到 go.mod,移除 logrus(vendor 中 wechat 等三方库自带的 logrus 不影响)
|
||||
status: completed
|
||||
- id: rewrite-log-package
|
||||
content: 重写 log/ 包:基于 zerolog 实现 Logger 结构体、NewLogger 工厂、CallerMarshalFunc 智能调用者过滤、TemplateFileWriter 带缓冲和模板路径切换
|
||||
status: completed
|
||||
- id: delete-error-add-dbstate
|
||||
content: 删除 common/error.go(Error 类型不再需要)、新增 common/dbstate.go(DBState 包装 Query+Data+Error)、Logger 内置 ERROR 级别环形缓冲
|
||||
status: completed
|
||||
- id: update-application
|
||||
content: 改造 application.go:删除 mode 配置、Application.Log 类型变更、SetConfig 中日志初始化用 logLevel 控制一切、DB 缓存始终开启、Cache-Control 跟 logLevel 联动
|
||||
status: completed
|
||||
- id: update-db
|
||||
content: 改造 db/db.go 和 db/query.go:用 DBState 替换 LastQuery+LastData+LastErr、HoTimeDB.Log 类型变更、SQL 日志格式优化、消除重复打印
|
||||
status: completed
|
||||
- id: update-var-config
|
||||
content: 更新 var.go 默认配置(删 mode、logLevel 默认 3)和 ConfigNote、example 配置文件
|
||||
status: completed
|
||||
- id: update-codegen
|
||||
content: 适配 code/makecode.go 中的 Error 使用(移除 Logger 相关)
|
||||
status: completed
|
||||
- id: update-tests
|
||||
content: 适配 testing_helper.go 和 db/transaction_test.go 中的日志创建
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# HoTime 日志系统全面重写方案
|
||||
|
||||
## 一、现状问题分析
|
||||
|
||||
### 1. logrus 已不适合继续使用
|
||||
- logrus 已进入 **维护模式**,不再开发新功能,仅修复安全问题
|
||||
- 性能差:依赖 mutex 锁和反射,在高并发下显著慢于现代替代品
|
||||
- 当前使用版本 `v1.8.1`([go.mod](go.mod) line 13)
|
||||
|
||||
### 2. 现有日志配置形同虚设
|
||||
- `logLevel` 在 [var.go](var.go) line 59 有定义和注释,但 **从未在代码中接入 logrus**
|
||||
- `mode` 承担了过多职责(0/1/2/3 四种),但代码中实际只区分 `== 0` 和 `!= 0`,且 mode 2/3 无独立分支
|
||||
|
||||
### 3. Error 类型与日志耦合导致重复打印
|
||||
- `SetError` 内部隐式 `Warn` 打印 + `query.go` defer 再打一次 = **同一错误打印两次**
|
||||
- `LastQuery`/`LastData`/`LastErr` 用不同的锁保护,存在读到不一致状态的风险
|
||||
|
||||
---
|
||||
|
||||
## 二、日志库选型:zerolog
|
||||
|
||||
**推荐 [zerolog](https://github.com/rs/zerolog)**:
|
||||
- 零内存分配,性能远超 logrus(快 5-10 倍)
|
||||
- 内置 `Caller()`、`ConsoleWriter`(彩色)、`MultiLevelWriter`(多输出)
|
||||
- 无 Go 版本要求(go.mod 保持 `go 1.16`)
|
||||
- 不选 `log/slog`:需 Go 1.21+,升级成本太大
|
||||
|
||||
---
|
||||
|
||||
## 三、配置参数重新设计
|
||||
|
||||
### 删除 `mode`
|
||||
|
||||
`mode` 原来控制的行为全部转移:
|
||||
- **DB 缓存**:`mode` 的拦截是多余的,cache 本身已是"配置即启用"设计(`cache.memory.db`/`cache.redis.db` 等控制)。删除 `mode` 后 `SetCache` 中始终挂载 `that.Db.HoTimeCache`,是否真正缓存由 cache 配置决定
|
||||
- **Cache-Control**:跟 `logLevel` 联动 -- `logLevel > 0` 时设 `no-cache`,`logLevel == 0` 时 `public`
|
||||
- **SQL 日志打印**:完全由 `logLevel` 控制(`logLevel > 0` 时打印 SQL)
|
||||
- **代码生成**:本来就由 `codeConfig[].mode` 独立控制,不受影响
|
||||
|
||||
### `logLevel` -- 保留,真正接管日志系统
|
||||
|
||||
- `0` = 仅 error(生产环境推荐)
|
||||
- `>=1` = 全部打印(info/warn/error/debug/SQL 全开)
|
||||
- **默认值 `1`**
|
||||
|
||||
简单二分:要么只看错误,要么全看。Cache-Control 同理:`logLevel==0` 时 `public`,`>0` 时 `no-cache`。
|
||||
|
||||
### `logFile` -- 保留
|
||||
|
||||
路径模板不变(`a/b/c/20060102150405.txt`),应用日志写入此文件。
|
||||
|
||||
### `logHistory` -- 新增
|
||||
|
||||
Logger 错误历史缓冲条数,默认 `100`。设为 `0` 关闭历史记录。可根据设备内存情况调小。
|
||||
|
||||
### `webConnectLogShow` / `webConnectLogFile` -- 保留独立
|
||||
|
||||
保留访问日志独立的理由(业务角度):
|
||||
- 访问日志量远大于应用日志,混在一起文件膨胀、查找困难
|
||||
- 生产环境需要不同的轮转策略
|
||||
- Nginx/Apache 都是 access.log 和 error.log 分开的行业惯例
|
||||
|
||||
配置保持不变:
|
||||
- `webConnectLogShow`:默认 true
|
||||
- `webConnectLogFile`:独立文件路径
|
||||
|
||||
---
|
||||
|
||||
## 四、新日志包设计 (`log/`)
|
||||
|
||||
### 核心结构
|
||||
|
||||
```go
|
||||
package log
|
||||
|
||||
type Logger struct {
|
||||
zl zerolog.Logger
|
||||
}
|
||||
|
||||
func NewLogger(logLevel int, logFile string, showCaller bool) *Logger
|
||||
|
||||
func (l *Logger) Debug() *zerolog.Event
|
||||
func (l *Logger) Info() *zerolog.Event
|
||||
func (l *Logger) Warn() *zerolog.Event
|
||||
func (l *Logger) Error() *zerolog.Event
|
||||
|
||||
func (l *Logger) Debugf(format string, v ...interface{})
|
||||
func (l *Logger) Infof(format string, v ...interface{})
|
||||
func (l *Logger) Warnf(format string, v ...interface{})
|
||||
func (l *Logger) Errorf(format string, v ...interface{})
|
||||
```
|
||||
|
||||
### 控制台输出格式
|
||||
|
||||
```
|
||||
2026-04-12 15:04:05 |INFO| 用户登录成功 [app/admin/user.go:45]
|
||||
2026-04-12 15:04:05 |WARN| 数据库连接超时 [app/admin/user.go:78]
|
||||
2026-04-12 15:04:05 |ERROR| 支付回调失败 order_id=456 [app/payment/callback.go:32]
|
||||
```
|
||||
|
||||
格式:`时间 | 类型标志 | 日志内容 | [代码位置]`,用 `[file:line]` 而非 logrus 的 `line="file:line"`。
|
||||
|
||||
### 文件输出格式(JSON)
|
||||
|
||||
```json
|
||||
{"time":"2026-04-12T15:04:05+08:00","level":"info","caller":"app/admin/user.go:45","message":"用户登录成功"}
|
||||
```
|
||||
|
||||
### 调用者智能过滤
|
||||
|
||||
复用并优化现有 [log/logrus.go](log/logrus.go) 中 `findCaller` / `isHoTimeFrameworkFile` 逻辑,实现为 zerolog 的 `CallerMarshalFunc`。
|
||||
|
||||
### 文件写入优化
|
||||
|
||||
`TemplateFileWriter`:带缓冲的 Writer,按时间模板自动切换文件路径,保持 `time.Now().Format(path)` 语义。
|
||||
|
||||
---
|
||||
|
||||
## 五、删除 Error 类型 + DBState + Logger 错误历史
|
||||
|
||||
### 删除 common.Error
|
||||
|
||||
`common.Error` 完全删除,不再需要。原因:
|
||||
|
||||
- Error 存在的意义是"存错误 + 自动打印",打印已移到 Log,只剩存储
|
||||
- 审查全部用法后,每个 `SetError` 调用点都可以直接用 `Log.Error()` 替代
|
||||
- `InitDb` 返回 `*Error` 从未被使用(调用方都是 `_ = that.InitDb(...)`)
|
||||
- `ConnectFunc(err ...*Error)` 可简化为返回 error
|
||||
- 需要"查最近的错误"由 Logger 错误历史替代
|
||||
|
||||
### Logger 错误历史(环形缓冲)
|
||||
|
||||
Logger 内置 ERROR 级别记录的环形缓冲(默认 100 条),打印即存储,零额外操作:
|
||||
|
||||
```go
|
||||
type ErrorRecord struct {
|
||||
Err error
|
||||
Msg string
|
||||
Time time.Time
|
||||
Caller string
|
||||
}
|
||||
|
||||
// Logger 内部
|
||||
type Logger struct {
|
||||
zl zerolog.Logger
|
||||
errors []ErrorRecord // 环形缓冲
|
||||
errorsMu sync.RWMutex
|
||||
maxErrors int // 默认 100
|
||||
}
|
||||
|
||||
// 每次 Log.Error() 自动存入缓冲
|
||||
// 获取最近 N 条错误(不传则返回全部已存储的,不超过 maxErrors)
|
||||
func (l *Logger) GetRecentErrors(n ...int) []ErrorRecord
|
||||
```
|
||||
|
||||
缓冲大小通过配置项 `logHistory` 控制,默认 100,可根据设备内存调整。
|
||||
|
||||
应用层使用:
|
||||
```go
|
||||
// 正常打印,自动存入历史
|
||||
that.Log.Error().Err(err).Msg("支付失败")
|
||||
|
||||
// 获取最近 10 条错误
|
||||
for _, e := range that.Log.GetRecentErrors(10) {
|
||||
fmt.Printf("[%s] %s: %v %s\n", e.Time.Format("15:04:05"), e.Msg, e.Err, e.Caller)
|
||||
}
|
||||
|
||||
// 获取全部已存储的错误
|
||||
all := that.Log.GetRecentErrors()
|
||||
```
|
||||
|
||||
### DBState -- DB 操作状态原子包装(新增)
|
||||
|
||||
新增 [common/dbstate.go](common/dbstate.go),将 `LastQuery` + `LastData` + `LastErr` 合为一体,一把锁保护原子读写。
|
||||
|
||||
**核心语义**:记录最后一次 DB 操作的完整状态。每次 SQL 执行后更新,用于判断"SQL 执行失败"还是"执行成功但结果为空"(如 UPDATE 影响 0 行)。
|
||||
|
||||
```go
|
||||
// DBSnapshot 操作状态快照(只读,安全传递)
|
||||
type DBSnapshot struct {
|
||||
Query string
|
||||
Data []interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
type DBState struct {
|
||||
query string
|
||||
data []interface{}
|
||||
err error
|
||||
mu sync.RWMutex // 零值即可用
|
||||
}
|
||||
|
||||
// Set 原子更新完整状态(每次 Query/Exec 执行后调用)
|
||||
func (s *DBState) Set(query string, data []interface{}, err error) {
|
||||
s.mu.Lock()
|
||||
s.query = query
|
||||
s.data = data
|
||||
s.err = err
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// Get 返回一致性快照(query/data/err 保证是同一次操作的)
|
||||
func (s *DBState) Get() DBSnapshot {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return DBSnapshot{Query: s.query, Data: s.data, Err: s.err}
|
||||
}
|
||||
|
||||
// SetError 只更新错误(Ping 等无 SQL 的场景)
|
||||
func (s *DBState) SetError(err error) {
|
||||
s.mu.Lock()
|
||||
s.err = err
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// GetError 快速判断最后操作是否出错
|
||||
func (s *DBState) GetError() error {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.err
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
**使用场景**:
|
||||
```go
|
||||
db.Exec("UPDATE stats SET count=count+1 WHERE id=?", id)
|
||||
rows, _ := result.RowsAffected()
|
||||
if rows == 0 {
|
||||
if state := db.GetLastState(); state != nil {
|
||||
// SQL 执行失败,state.Err 有具体错误
|
||||
} else {
|
||||
// SQL 成功,只是没有匹配的行
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### HoTimeDB 结构体变更
|
||||
|
||||
`HoTimeDB` 中原来的三个独立字段合并为私有 `lastState`,通过方法暴露,同时删除 `Mode`:
|
||||
|
||||
```go
|
||||
type HoTimeDB struct {
|
||||
*sql.DB
|
||||
// ...
|
||||
Log *log.Logger // 从 *logrus.Logger 改为新 Logger
|
||||
lastState DBState // 私有,替换 LastQuery + LastData + LastErr
|
||||
// 删除: Mode int
|
||||
// 删除: LastQuery string
|
||||
// 删除: LastData []interface{}
|
||||
// 删除: LastErr *Error
|
||||
}
|
||||
|
||||
// 唯一对外方法:nil = 成功,非 nil = 失败(附带完整上下文)
|
||||
func (db *HoTimeDB) GetLastState() *DBSnapshot
|
||||
```
|
||||
|
||||
内部逻辑:`lastState` 始终记录 query/data/err(供日志使用),但 `GetLastState()` 只在有错误时返回非 nil:
|
||||
```go
|
||||
func (db *HoTimeDB) GetLastState() *DBSnapshot {
|
||||
db.lastState.mu.RLock()
|
||||
defer db.lastState.mu.RUnlock()
|
||||
if db.lastState.err == nil {
|
||||
return nil // 没错误,不需要关心细节
|
||||
}
|
||||
return &DBSnapshot{
|
||||
Query: db.lastState.query,
|
||||
Data: db.lastState.data,
|
||||
Err: db.lastState.err,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
应用层使用:
|
||||
```go
|
||||
// 一行判断 + 取详情
|
||||
if state := db.GetLastState(); state != nil {
|
||||
// SQL 出错了,state.Err / state.Query / state.Data 全有
|
||||
that.Log.Error().Err(state.Err).Str("sql", state.Query).Msg("SQL 执行失败")
|
||||
}
|
||||
// nil = SQL 执行成功,不用管
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、DB 日志格式优化
|
||||
|
||||
### 修复重复打印
|
||||
|
||||
`SetError` 不再打印日志,`query.go` defer 统一负责 SQL 日志 -- 问题根除。
|
||||
|
||||
### 输出格式
|
||||
|
||||
**无错误时(DEBUG 级别):**
|
||||
```
|
||||
2026-04-12 15:04:05 |DEBUG| SQL: SELECT id,worker_id FROM `dingtalk` WHERE `state`=? DATA: [0] [dd/sync.go:63]
|
||||
```
|
||||
|
||||
**有错误时(自动升为 ERROR 级别):**
|
||||
```
|
||||
2026-04-12 15:04:05 |ERROR| SQL: SELECT id,worker_id FROM `dingtalk` WHERE `state`=? DATA: [0] ERROR: connection refused [dd/sync.go:63]
|
||||
```
|
||||
|
||||
关键改动:
|
||||
- 无错误时不显示 `ERROR:<nil>`
|
||||
- 有错误时日志级别自动升为 ERROR(而非一律 INFO)
|
||||
- SQL 日志在 `logLevel > 0` 时输出,`logLevel == 0` 时只有 SQL 出错才打印
|
||||
|
||||
### 测试模式下 SQL 错误自动 fail
|
||||
|
||||
测试框架中 SQL 出错让测试用例直接失败报错。
|
||||
|
||||
---
|
||||
|
||||
## 七、需要修改的文件清单
|
||||
|
||||
### 核心改动
|
||||
|
||||
- [log/logrus.go](log/logrus.go) -- 全部重写为 zerolog 实现,重命名为 `log/logger.go`
|
||||
- [common/error.go](common/error.go) -- **删除整个文件**
|
||||
- 新增 [common/dbstate.go](common/dbstate.go) -- DBState + DBSnapshot 类型
|
||||
- [application.go](application.go) -- 删除 `Error` 嵌入和所有 `Error{}` 初始化、删除 mode 逻辑、`Application.Log` 类型变更、`SetCache` 去掉 mode 判断、Cache-Control 改跟 logLevel、删除 `Db.Mode` 赋值(3 处)、`ConnectFunc` 签名简化、logLevel 接入
|
||||
- [var.go](var.go) -- 删除 `"mode": 2` 默认值、ConfigNote 删除 mode 说明、logLevel 默认值改为 3 并更新说明
|
||||
- [db/db.go](db/db.go) -- 删除 `Mode int` / `LastQuery` / `LastData` / `LastErr` 字段,改为 `LastState DBState` + `Log *log.Logger`,移除 logrus import
|
||||
- [db/query.go](db/query.go) -- 用 `LastState.Set()` 原子写入替换分散的赋值、SQL 日志格式优化(无错不显示 ERROR、有错升 ERROR 级别)、`Mode != 0` 改为 Logger 级别过滤
|
||||
- [go.mod](go.mod) -- 添加 zerolog 依赖
|
||||
|
||||
### 适配改动
|
||||
|
||||
- [db/crud.go](db/crud.go) -- `that.LastErr.SetError(e)` 改为 `that.lastState.SetError(e)`
|
||||
- [db/transaction.go](db/transaction.go) -- 事务拷贝中 `LastQuery`/`LastData`/`LastErr` 改为 `lastState`
|
||||
- [db/transaction_test.go](db/transaction_test.go) -- 删除 `Error{Logger: logger}`、logrus 替换
|
||||
- [code/makecode.go](code/makecode.go) -- 删除嵌入的 `Error`,改用 `*Logger` 打印错误
|
||||
- [cache/cache.go](cache/cache.go) -- `Init` 参数删除 `*Error`,改传 `*Logger`
|
||||
- [cache/cache_redis.go](cache/cache_redis.go) -- `that.Error.SetError(err)` 全部改为 `that.Log.Error().Err(err).Msg(...)`
|
||||
- [testing_helper.go](testing_helper.go) -- `app.Db.Mode` 删除、Logger 创建适配
|
||||
- [context.go](context.go) -- `that.Error.SetError(...)` 改为 `that.Log.Error().Msg(...)`
|
||||
- [example/app/test.go](example/app/test.go) -- `that.Db.LastQuery` 改为 `that.Db.GetLastState()` 相关访问
|
||||
- [example/app/mysql.go](example/app/mysql.go) -- 同上
|
||||
- [example/batch_cache_tester.go](example/batch_cache_tester.go) -- `&Error{}` 删除
|
||||
- [example/config/configNote.json](example/config/configNote.json) -- 删除 mode 说明、更新 logLevel 说明
|
||||
- [example/config/config.json](example/config/config.json) -- 删除 mode
|
||||
|
||||
---
|
||||
|
||||
## 八、应用层使用示例
|
||||
|
||||
```go
|
||||
// 基本使用
|
||||
that.Log.Infof("用户 %s 登录成功", name)
|
||||
that.Log.Errorf("订单 %d 支付失败: %v", orderId, err)
|
||||
|
||||
// 链式调用(附加结构化字段)
|
||||
that.Log.Info().Str("user", name).Int("age", 25).Msg("用户登录成功")
|
||||
that.Log.Error().Err(err).Str("order", orderId).Msg("支付回调失败")
|
||||
|
||||
// 打印即存储(Log.Error 自动存入错误历史)
|
||||
that.Log.Error().Err(err).Msg("数据库连接失败")
|
||||
|
||||
// DB 状态:nil = 成功,非 nil = 失败
|
||||
if state := that.Db.GetLastState(); state != nil {
|
||||
that.Log.Error().Err(state.Err).Str("sql", state.Query).Msg("SQL 失败")
|
||||
}
|
||||
|
||||
// 调阅最近 5 条错误(调试用,包括 DB 错误在内的所有 ERROR 级别日志)
|
||||
errors := that.Log.GetRecentErrors(5)
|
||||
|
||||
// SQL 日志由框架自动处理,logLevel>0 时输出
|
||||
// 无错误: |INFO| SQL: SELECT... DATA: [1,2] [app/user.go:45]
|
||||
// 有错误: |ERROR| SQL: SELECT... DATA: [1,2] ERROR: xxx [app/user.go:45]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、向后兼容与影响范围
|
||||
|
||||
### 编译级 breaking changes
|
||||
|
||||
- `common.Error` 类型完全删除:所有嵌入 `Error` 或传递 `*Error` 的地方改为 `*Logger` 或 `error`
|
||||
- `Application.Log` 从 `*logrus.Logger` 改为 `*log.Logger`
|
||||
- `Application.Error` 字段删除
|
||||
- `ConnectFunc` 签名简化(不再传递 `*Error`)
|
||||
- `HoTimeDB.LastQuery`/`LastData`/`LastErr`/`Mode` 删除:
|
||||
- 应用层通过 `db.GetLastState()` 获取(nil=成功)
|
||||
- 框架内部通过 `db.lastState.SetError(e)` 更新
|
||||
- 删除 `mode` 配置
|
||||
|
||||
### 不受影响
|
||||
|
||||
- `codeConfig[].mode`(代码生成模式)完全不受影响
|
||||
- `modeRouterStrict`(路由严格模式)完全不受影响
|
||||
- `cache` 配置结构不受影响(`cache.db.mode` 等是缓存自己的 mode,与全局 mode 无关)
|
||||
- `dri/mongodb` 的 `LastErr error` 是独立的普通 error,不受影响
|
||||
Reference in New Issue
Block a user