日志自主化:实现 DB 包自维护日志结构,利用 log.Log 自动填充机制 (by AI)

This commit is contained in:
AI Engineer 2026-05-05 17:59:45 +08:00
parent 174345dba8
commit bc3247dc4f
8 changed files with 91 additions and 24 deletions

View File

@ -97,7 +97,7 @@ func flatArgs(args []any) []any {
argValue := reflect.ValueOf(arg) argValue := reflect.ValueOf(arg)
kind := argValue.Kind() kind := argValue.Kind()
if kind == reflect.Map || kind == reflect.Struct || (kind == reflect.Slice && argValue.Type().Elem().Kind() != reflect.Uint8) { if kind == reflect.Map || kind == reflect.Struct || (kind == reflect.Slice && argValue.Type().Elem().Kind() != reflect.Uint8) {
args[i] = cast.MustToJSON(arg) args[i] = cast.As(cast.ToJSON(arg))
} }
} }
return args return args

View File

@ -1,5 +1,12 @@
# 变更记录 - @go/db # 变更记录 - @go/db
## [1.0.5] - 2026-05-05
### 优化
- **日志自主化**:
- 将数据库日志逻辑从 `log` 包迁移至 `db` 包,实现日志格式与业务逻辑的深度绑定。
- 自定义 `DBLog` 结构,利用 `log.GetEntry[DBLog]()` 对象池加速,并支持完整的错误堆栈捕获。
- 接入 `log.Log` 的自动填充能力,精简元数据维护逻辑。
## [1.0.4] - 2026-05-04 ## [1.0.4] - 2026-05-04
### 优化 ### 优化
- **日志增强**:升级 `apigo.cc/go/log` 至 v1.0.1,并重构数据库日志逻辑,利用新版 `log.DB` API 直接支持错误字段和调用栈捕获,提升排障效率。 - **日志增强**:升级 `apigo.cc/go/log` 至 v1.0.1,并重构数据库日志逻辑,利用新版 `log.DB` API 直接支持错误字段和调用栈捕获,提升排障效率。

16
DB.go
View File

@ -237,15 +237,15 @@ type dbLogger struct {
} }
func (dl *dbLogger) LogError(errStr string) { func (dl *dbLogger) LogError(errStr string) {
dl.logger.DB(dl.config.Type, dl.config.Dsn(), "", nil, 0, errStr) dl.LogDB("", nil, 0, errors.New(errStr))
} }
func (dl *dbLogger) LogQuery(query string, args []any, usedTime float32) { func (dl *dbLogger) LogQuery(query string, args []any, usedTime float32) {
dl.logger.DB(dl.config.Type, dl.config.Dsn(), query, args, usedTime) dl.LogDB(query, args, usedTime, nil)
} }
func (dl *dbLogger) LogQueryError(errStr string, query string, args []any, usedTime float32) { func (dl *dbLogger) LogQueryError(errStr string, query string, args []any, usedTime float32) {
dl.logger.DB(dl.config.Type, dl.config.Dsn(), query, args, usedTime, errStr) dl.LogDB(query, args, usedTime, errors.New(errStr))
} }
var dbConfigs = make(map[string]*Config) var dbConfigs = make(map[string]*Config)
@ -378,7 +378,7 @@ func getDB(name string, logger *log.Logger, useCache bool) *DB {
if n == 0 { if n == 0 {
once.Do(func() { once.Do(func() {
dbConfigs1 := make(map[string]*Config) dbConfigs1 := make(map[string]*Config)
if err := config.Load("db", &dbConfigs1); err == nil { if err := config.Load(&dbConfigs1, "db"); err == nil {
for k, v := range dbConfigs1 { for k, v := range dbConfigs1 {
if v.Host != "" { if v.Host != "" {
dbConfigsLock.Lock() dbConfigsLock.Lock()
@ -390,7 +390,7 @@ func getDB(name string, logger *log.Logger, useCache bool) *DB {
logger.Error(err.Error()) logger.Error(err.Error())
} }
dbConfigs2 := make(map[string]string) dbConfigs2 := make(map[string]string)
if err := config.Load("db", &dbConfigs2); err == nil { if err := config.Load(&dbConfigs2, "db"); err == nil {
for k, v := range dbConfigs2 { for k, v := range dbConfigs2 {
if strings.Contains(v, "://") { if strings.Contains(v, "://") {
v2 := new(Config) v2 := new(Config)
@ -434,7 +434,7 @@ func getDB(name string, logger *log.Logger, useCache bool) *DB {
} }
if conf.SSL != "" && len(dbSSLs) == 0 { if conf.SSL != "" && len(dbSSLs) == 0 {
_ = config.Load("dbssl", &dbSSLs) _ = config.Load(&dbSSLs, "dbssl")
} }
if conf.SSL != "" && dbSSLs[conf.SSL] == nil { if conf.SSL != "" && dbSSLs[conf.SSL] == nil {
@ -467,7 +467,7 @@ func getDB(name string, logger *log.Logger, useCache bool) *DB {
conn, err := getPool(conf) conn, err := getPool(conf)
if err != nil { if err != nil {
logger.DB(conf.Type, conf.Dsn(), "", nil, 0, err.Error()) LogDB(logger, conf, "", nil, 0, err)
return &DB{conn: nil, QuoteTag: "\"", Error: err} return &DB{conn: nil, QuoteTag: "\"", Error: err}
} }
@ -483,7 +483,7 @@ func getDB(name string, logger *log.Logger, useCache bool) *DB {
for _, host := range conf.ReadonlyHosts { for _, host := range conf.ReadonlyHosts {
conn, err := getPoolForHost(conf, host) conn, err := getPoolForHost(conf, host)
if err != nil { if err != nil {
logger.DB(conf.Type, conf.Dsn(), "", nil, 0, err.Error()) LogDB(logger, conf, "", nil, 0, err)
} else { } else {
readonlyConnections = append(readonlyConnections, conn) readonlyConnections = append(readonlyConnections, conn)
} }

View File

@ -409,7 +409,7 @@ func TestTransaction(t *testing.T) {
} }
n2 := countConnection() n2 := countConnection()
fmt.Println("# connection count", n1, n2, cast.MustToJSON(dbInst.GetOriginDB().Stats()), ".") fmt.Println("# connection count", n1, n2, cast.As(cast.ToJSON(dbInst.GetOriginDB().Stats())), ".")
} }
func countConnection() int { func countConnection() int {
@ -480,5 +480,5 @@ func BenchmarkForPoolParallel(b *testing.B) {
b.Log("OpenConnections", dbInst.GetOriginDB().Stats().OpenConnections) b.Log("OpenConnections", dbInst.GetOriginDB().Stats().OpenConnections)
n2 := countConnection() n2 := countConnection()
fmt.Println("# connection count", n1, n2, cast.MustToJSON(dbInst.GetOriginDB().Stats()), ".") fmt.Println("# connection count", n1, n2, cast.As(cast.ToJSON(dbInst.GetOriginDB().Stats())), ".")
} }

55
Log.go Normal file
View File

@ -0,0 +1,55 @@
package db
import (
"apigo.cc/go/cast"
"apigo.cc/go/log"
)
type DBLog struct {
log.BaseLog
DbType string
Dsn string
Query string
QueryArgs string
UsedTime float32
Error string
CallStacks []string
}
func (dl *dbLogger) LogDB(query string, args []any, usedTime float32, err error, extra ...any) {
LogDB(dl.logger, dl.config, query, args, usedTime, err, extra...)
}
func LogDB(logger *log.Logger, conf *Config, query string, args []any, usedTime float32, err error, extra ...any) {
if logger == nil {
return
}
logType := log.LogTypeDb
level := log.INFO
var e string
if err != nil {
logType = log.LogTypeDbError
level = log.ERROR
e = err.Error()
}
if logger.CheckLevel(level) {
entry := log.GetEntry[DBLog]()
// 仅关注业务字段LogType 手动赋值,基础字段由 logger.Log 自动填充
entry.LogType = logType
entry.DbType = conf.Type
entry.Dsn = conf.Dsn()
entry.Query = query
entry.QueryArgs = cast.To[string](args)
entry.UsedTime = usedTime
if e != "" {
entry.Error = e
entry.CallStacks = logger.GetCallStacks()
}
if len(extra) > 0 {
cast.FillMap(&entry.Extra, extra)
}
logger.Log(entry)
}
}

View File

@ -10,7 +10,6 @@ import (
"time" "time"
"apigo.cc/go/cast" "apigo.cc/go/cast"
"apigo.cc/go/convert"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
) )
@ -224,7 +223,7 @@ func (r *QueryResult) ToKV(target any) error {
} }
for _, item := range list { for _, item := range list {
newKey := reflect.ValueOf(reflect.New(t.Key()).Interface()).Elem() newKey := reflect.ValueOf(reflect.New(t.Key()).Interface()).Elem()
convert.To(item[colTypes[0].Name()], newKey.Addr().Interface()) cast.Convert(newKey.Addr().Interface(), item[colTypes[0].Name()])
newValue := v.MapIndex(newKey) newValue := v.MapIndex(newKey)
isNew := false isNew := false
@ -439,10 +438,10 @@ func (r *QueryResult) makeResults(results any, rows *sql.Rows) error {
if s != "" { if s != "" {
_ = json.Unmarshal([]byte(s), storedValue) _ = json.Unmarshal([]byte(s), storedValue)
} }
convert.To(storedValue, convertedObject.Interface()) cast.Convert(convertedObject.Interface(), storedValue)
field.Set(convertedObject.Elem()) field.Set(convertedObject.Elem())
} else { } else {
convert.To(val.Interface(), convertedObject.Interface()) cast.Convert(convertedObject.Interface(), val.Interface())
} }
} }
} else if field.Type().AssignableTo(val.Type()) { } else if field.Type().AssignableTo(val.Type()) {

12
go.mod
View File

@ -3,13 +3,13 @@ module apigo.cc/go/db
go 1.25.0 go 1.25.0
require ( require (
apigo.cc/go/cast v1.1.1 apigo.cc/go/cast v1.2.6
apigo.cc/go/config v1.0.4 apigo.cc/go/config v1.0.5
apigo.cc/go/convert v1.0.4
apigo.cc/go/crypto v1.0.4 apigo.cc/go/crypto v1.0.4
apigo.cc/go/id v1.0.4 apigo.cc/go/id v1.0.4
apigo.cc/go/log v1.0.1 apigo.cc/go/log v1.1.1
apigo.cc/go/rand v1.0.4 apigo.cc/go/rand v1.0.4
apigo.cc/go/redis v1.0.3
apigo.cc/go/safe v1.0.4 apigo.cc/go/safe v1.0.4
apigo.cc/go/shell v1.0.4 apigo.cc/go/shell v1.0.4
github.com/go-sql-driver/mysql v1.10.0 github.com/go-sql-driver/mysql v1.10.0
@ -19,10 +19,12 @@ require (
) )
require ( require (
apigo.cc/go/convert v1.0.4 // indirect
apigo.cc/go/encoding v1.0.4 // indirect apigo.cc/go/encoding v1.0.4 // indirect
apigo.cc/go/file v1.0.4 // indirect apigo.cc/go/file v1.0.5 // indirect
filippo.io/edwards25519 v1.2.0 // indirect filippo.io/edwards25519 v1.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gomodule/redigo v1.9.3 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect

12
go.sum
View File

@ -1,5 +1,5 @@
apigo.cc/go/cast v1.1.1 h1:+5pluN8g1RK2J4byr2xkfOmEdKSmy1PByOqDOHtt/Ns= apigo.cc/go/cast v1.2.6 h1:xnWiaQAGsRCrnu1p8fIFQfg5HFSc7CxR+3ItiDIDMaY=
apigo.cc/go/cast v1.1.1/go.mod h1:vh9ZqISCmTUiyinkNMI/s4f045fRlDK3xC+nPWQYBzI= apigo.cc/go/cast v1.2.6/go.mod h1:lGlwImiOvHxG7buyMWhFzcdvQzmSaoKbmr7bcDfUpHk=
apigo.cc/go/config v1.0.4 h1:WG9zrQkqfFPkrKIL7RNvvAbbkuUBt1Av11ZP/aIfldM= apigo.cc/go/config v1.0.4 h1:WG9zrQkqfFPkrKIL7RNvvAbbkuUBt1Av11ZP/aIfldM=
apigo.cc/go/config v1.0.4/go.mod h1:obryzJiK6j7lQex/58d5eWYOGx5O5IABguqNWxyyXJo= apigo.cc/go/config v1.0.4/go.mod h1:obryzJiK6j7lQex/58d5eWYOGx5O5IABguqNWxyyXJo=
apigo.cc/go/convert v1.0.4 h1:5+qPjC3dlPB59GnWZRlmthxcaXQtKvN+iOuiLdJ1GvQ= apigo.cc/go/convert v1.0.4 h1:5+qPjC3dlPB59GnWZRlmthxcaXQtKvN+iOuiLdJ1GvQ=
@ -12,10 +12,12 @@ apigo.cc/go/file v1.0.4 h1:qCKegV7OYh7r0qc3jZjGA/aKh0vIHgmr1OEbhfEmGX8=
apigo.cc/go/file v1.0.4/go.mod h1:C9gNo7386iA21OiBmuWh6CznKWlVBDFkhE4f0H0Susg= apigo.cc/go/file v1.0.4/go.mod h1:C9gNo7386iA21OiBmuWh6CznKWlVBDFkhE4f0H0Susg=
apigo.cc/go/id v1.0.4 h1:w+JSdeVit52iefIUolrh1qLEZS9XqHNKr1UygFcgv+s= apigo.cc/go/id v1.0.4 h1:w+JSdeVit52iefIUolrh1qLEZS9XqHNKr1UygFcgv+s=
apigo.cc/go/id v1.0.4/go.mod h1:kg7QuceAKtGNzGWt0+pIIh8Qom1eMSWGb8+0Yhi/QVY= apigo.cc/go/id v1.0.4/go.mod h1:kg7QuceAKtGNzGWt0+pIIh8Qom1eMSWGb8+0Yhi/QVY=
apigo.cc/go/log v1.0.0 h1:lI1NGTSS+Jm12G8BD7ZJO4/hrkfuLTu5O8z36GD8GpU= apigo.cc/go/log v1.0.2 h1:OY6T3SC28blDNkMpdRvDK2N4sGdriAB9DBItGl/qOos=
apigo.cc/go/log v1.0.0/go.mod h1:tvPgFpebY9Wf/DlqMHZ0ZjxDp9AaQTywOQKvtBaNqNo= apigo.cc/go/log v1.0.2/go.mod h1:tvPgFpebY9Wf/DlqMHZ0ZjxDp9AaQTywOQKvtBaNqNo=
apigo.cc/go/rand v1.0.4 h1:we070eWSL0dB8NEMaWjXj43+EekXQTm/h0kKpZ/frqw= apigo.cc/go/rand v1.0.4 h1:we070eWSL0dB8NEMaWjXj43+EekXQTm/h0kKpZ/frqw=
apigo.cc/go/rand v1.0.4/go.mod h1:mZ/4Soa3bk+XvDaqPWJuUe1bfEi4eThBj1XmEAuYxsk= apigo.cc/go/rand v1.0.4/go.mod h1:mZ/4Soa3bk+XvDaqPWJuUe1bfEi4eThBj1XmEAuYxsk=
apigo.cc/go/redis v1.0.2 h1:gWBrL/6eDxtouTFSZrPKQNdEg1AZr2aKTpCOhwim3dI=
apigo.cc/go/redis v1.0.2/go.mod h1:auQ3cyORgD67HF5dNvZ1lA8bqMH1xIbnuKBuZWclNy4=
apigo.cc/go/safe v1.0.4 h1:07pRSdEHprF/2v6SsqAjICYFoeLcqjjvHGEdh6Dzrzg= apigo.cc/go/safe v1.0.4 h1:07pRSdEHprF/2v6SsqAjICYFoeLcqjjvHGEdh6Dzrzg=
apigo.cc/go/safe v1.0.4/go.mod h1:o568sHS5rTRSVPmhxWod0tGdc+8l1KjidsNY1/OVZr0= apigo.cc/go/safe v1.0.4/go.mod h1:o568sHS5rTRSVPmhxWod0tGdc+8l1KjidsNY1/OVZr0=
apigo.cc/go/shell v1.0.4 h1:EL9zjI39YBe1h+kRYQeAi/8zVGHe5W198DYYN7cENiY= apigo.cc/go/shell v1.0.4 h1:EL9zjI39YBe1h+kRYQeAi/8zVGHe5W198DYYN7cENiY=
@ -30,6 +32,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw= github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
github.com/gomodule/redigo v1.9.3 h1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=
github.com/gomodule/redigo v1.9.3/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=