From 965d98cb13ff2f658e3bf6fa918cc608214f9cc9 Mon Sep 17 00:00:00 2001 From: AI Engineer Date: Mon, 4 May 2026 09:57:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(log):=20=E5=BC=95=E5=85=A5=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E9=87=8D=E7=BD=AE=E6=9C=BA=E5=88=B6=EF=BC=8C?= =?UTF-8?q?=E7=B2=BE=E7=AE=80=E6=89=A9=E5=B1=95=E6=97=A5=E5=BF=97=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=8C=E6=94=AF=E6=8C=81=E5=8F=98=E9=95=BF=20extra?= =?UTF-8?q?=20=E5=8F=82=E6=95=B0=20(by=20AI)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++ README.md | 57 +++++++++------- TEST.md | 34 ++++++---- bench_new_test.go | 8 +-- extra.go | 133 ++++++++++++++++++++++--------------- log_test.go | 11 ++-- logger.go | 119 ++++++++++++--------------------- pool.go | 104 +++++++++++++++++++++++++---- pool_test.go | 12 +--- standard.go | 163 +--------------------------------------------- 10 files changed, 287 insertions(+), 360 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4f8c84..29c5de0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [1.0.2] - 2026-05-04 +- **设计优化**: 引入 `ResetLogEntry` 自动化重置机制,基于反射和缓存实现日志对象字段的自动初始化与清空(Map/Slice 默认容量 8)。 +- **接口精简**: 简化 `LogEntry` 接口为标记接口,移除了冗余的 `Base()` 和 `Reset()` 手动实现。 +- **扩展性增强**: `Task`, `Monitor`, `Statistic`, `DB` 等快捷方法全面支持变长 `extra ...any` 参数,并集成 `cast.ToMap` 自动转换。 +- **构建修复**: 修复了 `convert` 模块对 `cast` 新 API 的兼容性问题。 + ## [1.0.1] - 2026-05-04 - **结构增强**: `DBLog` 结构体新增 `Error` 和 `CallStacks` 字段,提升数据库错误诊断效率。 - **DB 方法重构**: `Logger.DB` 方法支持可选错误参数,自动处理 `dbError` 类型并记录调用栈。 diff --git a/README.md b/README.md index 476a1dd..f1d427d 100644 --- a/README.md +++ b/README.md @@ -4,51 +4,62 @@ ## 特性 - **零摩擦**: 自动从环境变量获取应用名、IP 等信息。 -- **高性能**: 异步写入,支持批量刷盘。 +- **高性能**: 异步写入,支持对象池化与批量刷盘。 +- **自动化**: 自定义日志类型只需嵌入 `BaseLog`,无需手动实现重置逻辑。 - **脱敏支持**: 内置敏感字段过滤与正则匹配脱敏。 - **多渠道**: 支持控制台、本地文件切分、Elasticsearch 批量写入。 -- **现代化**: 深度集成 `apigo.cc/go` 基础库。 ## 安装 ```bash go get apigo.cc/go/log ``` -## 标准化日志 API +## 基础 API +所有日志方法均支持变长额外参数,自动通过 `cast.ToMap` 转换为键值对存入 `Extra` 字段。 +```go +logger.Info("用户登录", "userId", 10086, "ip", "1.2.3.4") +logger.Error("数据库连接失败", "db", "mysql", "err", err) +``` -除了基础的 `Debug`, `Info`, `Warning`, `Error` 外,`go/log` 还提供了一系列针对特定场景优化的标准化日志 API: +## 扩展日志 API ### 数据库日志 (DB) 自动处理耗时计算、脱敏及错误堆栈捕获。 ```go // 记录正常 SQL -logger.DB("mysql", dsn, "SELECT * FROM users WHERE id=?", []any{1}, 10.5) +logger.DB("mysql", dsn, "SELECT * FROM users WHERE id=?", []any{1}, 10.5, nil) // 记录带错误的 SQL (自动捕获调用栈并设为 dbError 类型) -logger.DB("mysql", dsn, "SELECT...", args, usedTime, "table not found") -``` - -### 请求日志 (Request) -针对高性能 HTTP 服务设计的结构化日志。 -```go -req := &log.RequestLog{ - Method: "GET", - Path: "/api/user", - // ... 填充其他字段 -} -logger.Request(req) +logger.DB("mysql", dsn, "SELECT...", args, usedTime, err, "k1", "v1") ``` ### 任务与监控 (Task / Monitor / Statistic) ```go -// 任务执行日志 -logger.Task("CleanCache", 150.2, true, "Success") +// 任务执行日志 (任务名, 耗时ms, 是否成功, 消息, 额外参数...) +logger.Task("CleanCache", 150.2, true, "Success", "deleted", 100) -// 监控告警日志 -logger.Monitor("CPU", 1, "Load too high") +// 监控告警日志 (目标, 状态码, 消息, 额外参数...) +logger.Monitor("CPU", 1, "Load too high", "usage", "95%") -// 业务指标统计 -logger.Statistic("Business", "OrderCount", 100) +// 业务指标统计 (类别, 项目, 数值, 额外参数...) +logger.Statistic("Business", "OrderCount", 100, "region", "cn") +``` + +### 自定义日志类型 +只需嵌入 `BaseLog` 即可利用对象池和自动重置功能。 +```go +type MyBusinessLog struct { + log.BaseLog + OrderId string + Amount float64 +} + +// 使用方式 +entry := log.GetEntry(reflect.TypeOf(&MyBusinessLog{})).(*MyBusinessLog) +logger.fillBase(entry, "business") +entry.OrderId = "O123" +entry.Amount = 99.8 +logger.Log(entry) ``` ## 配置项 (JSON/YAML) diff --git a/TEST.md b/TEST.md index 272b53a..0f345d2 100644 --- a/TEST.md +++ b/TEST.md @@ -1,19 +1,27 @@ -# Log Performance Test +# 日志性能测试报告 -## Test Environment -- OS: darwin -- Arch: amd64 +## 测试环境 +- 操作系统: darwin +- 架构: amd64 - CPU: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz -## Benchmark Results (v1.0.1) +## 基准测试结果 (v1.0.2) -| Benchmark | Iterations | ns/op | B/op | allocs/op | +| 测试用例 | 迭代次数 | 耗时 (ns/op) | 内存分配 (B/op) | 分配次数 (allocs/op) | | :--- | :--- | :--- | :--- | :--- | -| `BenchmarkLogger_RequestLog_Realistic` | 4,937,952 | 270.2 | 72 | 2 | -| `BenchmarkLoggerInfo` | 108,744 | 9,699 | - | - | -| `BenchmarkLoggerAsyncConcurrent` | 121,032 | 8,891 | - | - | +| `BenchmarkLogger_RequestLog_Realistic` | 2,434,633 | 475.7 | 72 | 2 | +| `BenchmarkLoggerInfo` | 113,421 | 9,857 | - | - | +| `BenchmarkLoggerAsyncConcurrent` | 124,932 | 8,262 | - | - | -## Summary -- **RequestLog Performance**: High-performance structured logging with minimal allocations. -- **Async Efficiency**: Concurrent logging remains stable and efficient. -- **Object Pooling**: Effective use of `sync.Pool` for `LogEntry` objects reduces GC pressure. +## 版本对比评估 + +| 版本 | 机制 | 耗时 (ns/op) | 易用性 | +| :--- | :--- | :--- | :--- | +| **v1.0.1** | 手动 Reset | ~270 | 较低 (需编写大量样板代码) | +| **v1.0.2** | 自动化 Reset | ~475 | 极高 (嵌入 BaseLog 即可) | + +## 总结 +- **性能评估**: 引入自动化重置机制后,单次日志操作耗时增加了约 200ns。这主要是反射探测和函数缓存调用的开销。但在高性能生产环境中,亚微秒(< 1μs)级的延迟依然极其优秀。 +- **内存效率**: 内存分配保持在极低水平 (72B, 2次分配),说明对象池和 `reflect.Value.Clear()` 机制有效地控制了 GC 压力。 +- **开发体验**: 开发者现在只需通过嵌入 `BaseLog` 即可创建自定义日志类型,不再需要手动编写冗长的 `Reset()` 和 `Base()` 方法。 +- **优化点**: 采用了字段重置函数缓存,避免了每次日志记录都进行深度的反射解析。 diff --git a/bench_new_test.go b/bench_new_test.go index 67d1a37..dc77a0d 100644 --- a/bench_new_test.go +++ b/bench_new_test.go @@ -33,7 +33,7 @@ func BenchmarkLogger_RequestLog_Realistic(b *testing.B) { b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - WithEntry(typ, func(e LogEntry) { + WithEntry(typ, func(e any) { entry := e.(*RequestLog) entry.RequestId = "req-1234567890" entry.UsedTime = 45.67 @@ -41,15 +41,9 @@ func BenchmarkLogger_RequestLog_Realistic(b *testing.B) { entry.Method = "POST" entry.ResponseCode = 200 - if entry.RequestHeaders == nil { - entry.RequestHeaders = make(map[string]string) - } entry.RequestHeaders["Content-Type"] = "application/json" entry.RequestHeaders["Authorization"] = "Bearer token-value" - if entry.RequestData == nil { - entry.RequestData = make(map[string]any) - } entry.RequestData["userId"] = 10086 entry.RequestData["action"] = "update_profile" diff --git a/extra.go b/extra.go index 68e46fa..2ebfb66 100644 --- a/extra.go +++ b/extra.go @@ -6,6 +6,41 @@ import ( "apigo.cc/go/cast" ) +type RequestLog struct { + BaseLog + ServerId string + App string + Node string + ClientIp string + FromApp string + FromNode string + UserId string + DeviceId string + ClientAppName string + ClientAppVersion string + SessionId string + RequestId string + Host string + Scheme string + Proto string + AuthLevel int + Priority int + Method string + Path string + RequestHeaders map[string]string + RequestData map[string]any + UsedTime float32 + ResponseCode int + ResponseHeaders map[string]string + ResponseDataLength uint + ResponseData string +} + +func (logger *Logger) Request(entry *RequestLog) { + logger.fillBase(entry, LogTypeRequest) + logger.Log(entry) +} + type TaskLog struct { BaseLog Task string @@ -14,18 +49,6 @@ type TaskLog struct { Message string } -func (t *TaskLog) Reset() { - t.BaseLog.Reset() - t.Task = "" - t.UsedTime = 0 - t.Success = false - t.Message = "" -} - -func (t *TaskLog) Base() *BaseLog { - return &t.BaseLog -} - type MonitorLog struct { BaseLog Target string @@ -33,17 +56,6 @@ type MonitorLog struct { Message string } -func (m *MonitorLog) Reset() { - m.BaseLog.Reset() - m.Target = "" - m.Status = 0 - m.Message = "" -} - -func (m *MonitorLog) Base() *BaseLog { - return &m.BaseLog -} - type StatisticLog struct { BaseLog Category string @@ -51,31 +63,16 @@ type StatisticLog struct { Value float64 } -func (s *StatisticLog) Reset() { - s.BaseLog.Reset() - s.Category = "" - s.Item = "" - s.Value = 0 -} - -func (s *StatisticLog) Base() *BaseLog { - return &s.BaseLog -} - func (logger *Logger) Task(taskName string, usedTime float32, success bool, message string, extra ...any) { if logger.CheckLevel(INFO) { entry := GetEntry(reflect.TypeOf(&TaskLog{})).(*TaskLog) - logger.fillBase(entry.Base(), LogTypeTask) + logger.fillBase(entry, LogTypeTask) entry.Task = taskName entry.UsedTime = usedTime entry.Success = success entry.Message = message if len(extra) > 0 { - for i := 0; i < len(extra); i += 2 { - if i+1 < len(extra) { - entry.Extra[cast.String(extra[i])] = extra[i+1] - } - } + cast.ToMap(entry.Extra, extra) } logger.Log(entry) } @@ -84,16 +81,12 @@ func (logger *Logger) Task(taskName string, usedTime float32, success bool, mess func (logger *Logger) Monitor(target string, status int, message string, extra ...any) { if logger.CheckLevel(INFO) { entry := GetEntry(reflect.TypeOf(&MonitorLog{})).(*MonitorLog) - logger.fillBase(entry.Base(), LogTypeMonitor) + logger.fillBase(entry, LogTypeMonitor) entry.Target = target entry.Status = status entry.Message = message if len(extra) > 0 { - for i := 0; i < len(extra); i += 2 { - if i+1 < len(extra) { - entry.Extra[cast.String(extra[i])] = extra[i+1] - } - } + cast.ToMap(entry.Extra, extra) } logger.Log(entry) } @@ -102,16 +95,52 @@ func (logger *Logger) Monitor(target string, status int, message string, extra . func (logger *Logger) Statistic(category, item string, value float64, extra ...any) { if logger.CheckLevel(INFO) { entry := GetEntry(reflect.TypeOf(&StatisticLog{})).(*StatisticLog) - logger.fillBase(entry.Base(), LogTypeStatistic) + logger.fillBase(entry, LogTypeStatistic) entry.Category = category entry.Item = item entry.Value = value if len(extra) > 0 { - for i := 0; i < len(extra); i += 2 { - if i+1 < len(extra) { - entry.Extra[cast.String(extra[i])] = extra[i+1] - } - } + cast.ToMap(entry.Extra, extra) + } + logger.Log(entry) + } +} + +type DBLog struct { + BaseLog + DbType string + Dsn string + Query string + QueryArgs string + UsedTime float32 + Error string + CallStacks []string +} + +func (logger *Logger) DB(dbType, dsn, query string, args []any, usedTime float32, err error, extra ...any) { + logType := LogTypeDb + level := INFO + var e string + if err != nil { + logType = LogTypeDbError + level = ERROR + e = err.Error() + } + + if logger.CheckLevel(level) { + entry := GetEntry(reflect.TypeOf(&DBLog{})).(*DBLog) + logger.fillBase(entry, logType) + entry.DbType = dbType + entry.Dsn = dsn + entry.Query = query + entry.QueryArgs = cast.MustToJSON(args) + entry.UsedTime = usedTime + if e != "" { + entry.Error = e + entry.CallStacks = getCallStacks(logger.truncations) + } + if len(extra) > 0 { + cast.ToMap(entry.Extra, extra) } logger.Log(entry) } diff --git a/log_test.go b/log_test.go index aa9c330..514eaad 100644 --- a/log_test.go +++ b/log_test.go @@ -1,6 +1,7 @@ package log import ( + "fmt" "testing" ) @@ -32,13 +33,13 @@ func TestDBLog(t *testing.T) { }) // 测试普通 DB 日志 - logger.DB("mysql", "dsn...", "SELECT * FROM users", []any{1}, 10.5) + logger.DB("mysql", "dsn...", "SELECT * FROM users", []any{1}, 10.5, nil) - // 测试 DB 错误日志 - logger.DBError("connection lost", "mysql", "dsn...", "SELECT * FROM users", []any{1}, 10.5) + // 测试 DB 错误日志 (通过传递 error 对象) + logger.DB("mysql", "dsn...", "SELECT * FROM users", []any{1}, 10.5, fmt.Errorf("connection lost")) - // 测试合并后的 DB 方法带错误 - logger.DB("mysql", "dsn...", "SELECT * FROM users", []any{1}, 10.5, "another error") + // 测试带额外参数的 DB 日志 + logger.DB("mysql", "dsn...", "SELECT * FROM users", []any{1}, 10.5, nil, "k1", "v1") } func TestRequestLog(t *testing.T) { diff --git a/logger.go b/logger.go index 7e692c7..ad61936 100644 --- a/logger.go +++ b/logger.go @@ -162,8 +162,8 @@ func NewLogger(conf Config) *Logger { } func (logger *Logger) Log(data any) { - if entry, ok := data.(LogEntry); ok { - logger.asyncWrite(entry) + if entry, ok := data.(LogEntry); ok && entry.IsLogEntry() { + logger.asyncWrite(data) return } @@ -171,16 +171,16 @@ func (logger *Logger) Log(data any) { if err != nil { buf, _ = logger.formatter.Format(map[string]any{ - "logType": LogTypeUndefined, - "traceId": logger.traceId, - "undefined": fmt.Sprint(data), + "logType": LogTypeUndefined, + "traceId": logger.traceId, + "message": cast.String(data), }, nil) } logger.writeBuf(buf) } -func (logger *Logger) asyncWrite(entry LogEntry) { +func (logger *Logger) asyncWrite(entry any) { buf, err := logger.formatter.Format(entry, logger.sensitiveKeys) if err == nil { @@ -206,17 +206,42 @@ func (logger *Logger) writeBuf(buf []byte) { } } +func (logger *Logger) fillBase(entry any, logType string) { + var base *BaseLog + rv := reflect.ValueOf(entry) + if rv.Kind() == reflect.Ptr { + rv = rv.Elem() + } + if rv.Kind() == reflect.Struct { + f := rv.FieldByName("BaseLog") + if f.IsValid() && f.CanAddr() { + if b, ok := f.Addr().Interface().(*BaseLog); ok { + base = b + } + } + } + + if base == nil { + return + } + + base.LogName = logger.config.Name + base.LogType = logType + base.LogTime = MakeLogTime(time.Now()) + base.TraceId = logger.traceId + base.ImageName = dockerImageName + base.ImageTag = dockerImageTag + base.ServerName = serverName + base.ServerIp = serverIp +} + func (logger *Logger) Debug(message string, extra ...any) { if logger.CheckLevel(DEBUG) { entry := GetEntry(reflect.TypeOf(&DebugLog{})).(*DebugLog) - logger.fillBase(entry.Base(), LogTypeDebug) + logger.fillBase(entry, LogTypeDebug) entry.Debug = message if len(extra) > 0 { - for i := 0; i < len(extra); i += 2 { - if i+1 < len(extra) { - entry.Extra[cast.String(extra[i])] = extra[i+1] - } - } + cast.ToMap(entry.Extra, extra) } logger.Log(entry) } @@ -225,14 +250,10 @@ func (logger *Logger) Debug(message string, extra ...any) { func (logger *Logger) Info(message string, extra ...any) { if logger.CheckLevel(INFO) { entry := GetEntry(reflect.TypeOf(&InfoLog{})).(*InfoLog) - logger.fillBase(entry.Base(), LogTypeInfo) + logger.fillBase(entry, LogTypeInfo) entry.Info = message if len(extra) > 0 { - for i := 0; i < len(extra); i += 2 { - if i+1 < len(extra) { - entry.Extra[cast.String(extra[i])] = extra[i+1] - } - } + cast.ToMap(entry.Extra, extra) } logger.Log(entry) } @@ -241,15 +262,11 @@ func (logger *Logger) Info(message string, extra ...any) { func (logger *Logger) Warning(message string, extra ...any) { if logger.CheckLevel(WARNING) { entry := GetEntry(reflect.TypeOf(&WarningLog{})).(*WarningLog) - logger.fillBase(entry.Base(), LogTypeWarning) + logger.fillBase(entry, LogTypeWarning) entry.Warning = message entry.CallStacks = getCallStacks(logger.truncations) if len(extra) > 0 { - for i := 0; i < len(extra); i += 2 { - if i+1 < len(extra) { - entry.Extra[cast.String(extra[i])] = extra[i+1] - } - } + cast.ToMap(entry.Extra, extra) } logger.Log(entry) } @@ -258,15 +275,11 @@ func (logger *Logger) Warning(message string, extra ...any) { func (logger *Logger) Error(message string, extra ...any) { if logger.CheckLevel(ERROR) { entry := GetEntry(reflect.TypeOf(&ErrorLog{})).(*ErrorLog) - logger.fillBase(entry.Base(), LogTypeError) + logger.fillBase(entry, LogTypeError) entry.Error = message entry.CallStacks = getCallStacks(logger.truncations) if len(extra) > 0 { - for i := 0; i < len(extra); i += 2 { - if i+1 < len(extra) { - entry.Extra[cast.String(extra[i])] = extra[i+1] - } - } + cast.ToMap(entry.Extra, extra) } logger.Log(entry) } @@ -297,49 +310,3 @@ func (logger *Logger) CheckLevel(logLevel LevelType) bool { } return logLevel >= settedLevel } - -func (logger *Logger) fillBase(base *BaseLog, logType string) { - base.LogName = logger.config.Name - base.LogType = logType - base.LogTime = MakeLogTime(time.Now()) - base.TraceId = logger.traceId - base.ImageName = dockerImageName - base.ImageTag = dockerImageTag - base.ServerName = serverName - base.ServerIp = serverIp -} - -func (logger *Logger) DB(dbType, dsn, query string, args []any, usedTime float32, errStr ...string) { - logType := LogTypeDb - level := INFO - var e string - if len(errStr) > 0 && errStr[0] != "" { - logType = LogTypeDbError - level = ERROR - e = errStr[0] - } - - if logger.CheckLevel(level) { - entry := GetEntry(reflect.TypeOf(&DBLog{})).(*DBLog) - logger.fillBase(entry.Base(), logType) - entry.DbType = dbType - entry.Dsn = dsn - entry.Query = query - entry.QueryArgs = cast.MustToJSON(args) - entry.UsedTime = usedTime - if e != "" { - entry.Error = e - entry.CallStacks = getCallStacks(logger.truncations) - } - logger.Log(entry) - } -} - -func (logger *Logger) DBError(errStr, dbType, dsn, query string, args []any, usedTime float32) { - logger.DB(dbType, dsn, query, args, usedTime, errStr) -} - -func (logger *Logger) Request(entry *RequestLog) { - logger.fillBase(entry.Base(), LogTypeRequest) - logger.Log(entry) -} diff --git a/pool.go b/pool.go index 0245d61..eaf00cf 100644 --- a/pool.go +++ b/pool.go @@ -5,33 +5,106 @@ import ( "sync" ) -// LogEntry 定义了高性能日志必须实现的接口 -type LogEntry interface { - Reset() - Base() *BaseLog -} - // PoolManager 管理不同日志类型的对象池 type PoolManager struct { pools sync.Map // map[reflect.Type]*sync.Pool } -var globalPools = &PoolManager{} +var ( + globalPools = &PoolManager{} + resetCache sync.Map // map[reflect.Type]func(reflect.Value) +) // GetEntry 从池中获取一个指定类型的日志对象,并确保其处于 Reset 后的干净状态 -func GetEntry(t reflect.Type) LogEntry { +func GetEntry(t reflect.Type) any { pool, _ := globalPools.pools.LoadOrStore(t, &sync.Pool{ New: func() any { return reflect.New(t.Elem()).Interface() }, }) - entry := pool.(*sync.Pool).Get().(LogEntry) - entry.Reset() // 确保获取到的对象永远是干净且预分配好的 + entry := pool.(*sync.Pool).Get() + ResetLogEntry(entry) // 自动重置所有字段,无需子类实现 Reset return entry } -// PutEntry 将日志对象归还到池中,不再进行 Reset -func PutEntry(entry LogEntry) { +// ResetLogEntry 使用反射自动化重置日志对象的所有字段 +// 特别是对 Map 和 Slice 进行初始化(长度0,容量8) +func ResetLogEntry(v any) { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + return + } + + t := rv.Type() + resetFunc, ok := resetCache.Load(t) + if !ok { + resetFunc = buildResetFunc(t.Elem()) + resetCache.Store(t, resetFunc) + } + resetFunc.(func(reflect.Value))(rv.Elem()) +} + +func buildResetFunc(t reflect.Type) func(reflect.Value) { + var funcs []func(reflect.Value) + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + fieldIdx := i + + switch field.Type.Kind() { + case reflect.String: + funcs = append(funcs, func(rv reflect.Value) { rv.Field(fieldIdx).SetString("") }) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + funcs = append(funcs, func(rv reflect.Value) { rv.Field(fieldIdx).SetInt(0) }) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + funcs = append(funcs, func(rv reflect.Value) { rv.Field(fieldIdx).SetUint(0) }) + case reflect.Float32, reflect.Float64: + funcs = append(funcs, func(rv reflect.Value) { rv.Field(fieldIdx).SetFloat(0) }) + case reflect.Bool: + funcs = append(funcs, func(rv reflect.Value) { rv.Field(fieldIdx).SetBool(false) }) + case reflect.Map: + funcs = append(funcs, func(rv reflect.Value) { + f := rv.Field(fieldIdx) + if f.IsNil() { + f.Set(reflect.MakeMapWithSize(f.Type(), 8)) + } else { + f.Clear() + } + }) + case reflect.Slice: + funcs = append(funcs, func(rv reflect.Value) { + f := rv.Field(fieldIdx) + if f.Cap() < 8 { + f.Set(reflect.MakeSlice(f.Type(), 0, 8)) + } else { + f.SetLen(0) + } + }) + case reflect.Struct: + subReset := buildResetFunc(field.Type) + funcs = append(funcs, func(rv reflect.Value) { + subReset(rv.Field(fieldIdx)) + }) + case reflect.Ptr, reflect.Interface: + zero := reflect.Zero(field.Type) + funcs = append(funcs, func(rv reflect.Value) { + rv.Field(fieldIdx).Set(zero) + }) + } + } + + return func(rv reflect.Value) { + for _, f := range funcs { + f(rv) + } + } +} + +func resetStruct(rv reflect.Value) { + // 已经不再直接调用,保留 buildResetFunc 逻辑即可 +} + +// PutEntry 将日志对象归还到池中 +func PutEntry(entry any) { t := reflect.TypeOf(entry) if pool, ok := globalPools.pools.Load(t); ok { pool.(*sync.Pool).Put(entry) @@ -39,8 +112,13 @@ func PutEntry(entry LogEntry) { } // WithEntry 执行闭包并在结束后自动回收对象 -func WithEntry(t reflect.Type, fn func(LogEntry)) { +func WithEntry(t reflect.Type, fn func(any)) { entry := GetEntry(t) defer PutEntry(entry) fn(entry) } + +// LogEntry 是一个标记接口,用于识别是否为对象池管理的日志对象 +type LogEntry interface { + IsLogEntry() bool +} diff --git a/pool_test.go b/pool_test.go index a141191..e4e0731 100644 --- a/pool_test.go +++ b/pool_test.go @@ -12,20 +12,10 @@ type MockRequestLog struct { UsedTime float32 } -func (m *MockRequestLog) Reset() { - m.BaseLog.Reset() - m.RequestId = "" - m.UsedTime = 0 -} - -func (m *MockRequestLog) Base() *BaseLog { - return &m.BaseLog -} - func TestWithEntry(t *testing.T) { typ := reflect.TypeOf(&MockRequestLog{}) - WithEntry(typ, func(e LogEntry) { + WithEntry(typ, func(e any) { entry := e.(*MockRequestLog) entry.RequestId = "with-entry-id" }) diff --git a/standard.go b/standard.go index e797faa..bb5ad44 100644 --- a/standard.go +++ b/standard.go @@ -29,23 +29,11 @@ type BaseLog struct { ImageTag string ServerName string ServerIp string - Extra map[string]interface{} + Extra map[string]any } -func (b *BaseLog) Reset() { - b.LogName = "" - b.LogType = "" - b.LogTime = "" - b.TraceId = "" - if b.Extra == nil { - b.Extra = make(map[string]interface{}, 8) - } else { - clear(b.Extra) - } -} - -func (b *BaseLog) Base() *BaseLog { - return b +func (b *BaseLog) IsLogEntry() bool { + return true } type DebugLog struct { @@ -53,164 +41,19 @@ type DebugLog struct { Debug string } -func (d *DebugLog) Reset() { - d.BaseLog.Reset() - d.Debug = "" -} - -func (d *DebugLog) Base() *BaseLog { - return &d.BaseLog -} - type InfoLog struct { BaseLog Info string } -func (i *InfoLog) Reset() { - i.BaseLog.Reset() - i.Info = "" -} - -func (i *InfoLog) Base() *BaseLog { - return &i.BaseLog -} - type WarningLog struct { BaseLog Warning string CallStacks []string } -func (w *WarningLog) Reset() { - w.BaseLog.Reset() - w.Warning = "" - w.CallStacks = w.CallStacks[:0] -} - -func (w *WarningLog) Base() *BaseLog { - return &w.BaseLog -} - type ErrorLog struct { BaseLog Error string CallStacks []string } - -func (e *ErrorLog) Reset() { - e.BaseLog.Reset() - e.Error = "" - e.CallStacks = e.CallStacks[:0] -} - -func (e *ErrorLog) Base() *BaseLog { - return &e.BaseLog -} - -type DBLog struct { - BaseLog - DbType string - Dsn string - Query string - QueryArgs string - UsedTime float32 - Error string - CallStacks []string -} - -func (d *DBLog) Reset() { - d.BaseLog.Reset() - d.DbType = "" - d.Dsn = "" - d.Query = "" - d.QueryArgs = "" - d.UsedTime = 0 - d.Error = "" - d.CallStacks = d.CallStacks[:0] -} - -func (d *DBLog) Base() *BaseLog { - return &d.BaseLog -} - -type RequestLog struct { - BaseLog - ServerId string - App string - Node string - ClientIp string - FromApp string - FromNode string - UserId string - DeviceId string - ClientAppName string - ClientAppVersion string - SessionId string - RequestId string - Host string - Scheme string - Proto string - AuthLevel int - Priority int - Method string - Path string - RequestHeaders map[string]string - RequestData map[string]any - UsedTime float32 - ResponseCode int - ResponseHeaders map[string]string - ResponseDataLength uint - ResponseData string -} - -func (r *RequestLog) Reset() { - r.BaseLog.Reset() - r.ServerId = "" - r.App = "" - r.Node = "" - r.ClientIp = "" - r.FromApp = "" - r.FromNode = "" - r.UserId = "" - r.DeviceId = "" - r.ClientAppName = "" - r.ClientAppVersion = "" - r.SessionId = "" - r.RequestId = "" - r.Host = "" - r.Scheme = "" - r.Proto = "" - r.AuthLevel = 0 - r.Priority = 0 - r.Method = "" - r.Path = "" - - if r.RequestHeaders == nil { - r.RequestHeaders = make(map[string]string, 8) - } else { - clear(r.RequestHeaders) - } - - if r.RequestData == nil { - r.RequestData = make(map[string]any, 8) - } else { - clear(r.RequestData) - } - - r.UsedTime = 0 - r.ResponseCode = 0 - - if r.ResponseHeaders == nil { - r.ResponseHeaders = make(map[string]string, 8) - } else { - clear(r.ResponseHeaders) - } - - r.ResponseDataLength = 0 - r.ResponseData = "" -} - -func (r *RequestLog) Base() *BaseLog { - return &r.BaseLog -}