feat(log): 引入自动化重置机制,精简扩展日志接口,支持变长 extra 参数 (by AI)
This commit is contained in:
parent
f9e3a2ec5a
commit
965d98cb13
@ -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` 类型并记录调用栈。
|
||||
|
||||
57
README.md
57
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)
|
||||
|
||||
34
TEST.md
34
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()` 方法。
|
||||
- **优化点**: 采用了字段重置函数缓存,避免了每次日志记录都进行深度的反射解析。
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
133
extra.go
133
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)
|
||||
}
|
||||
|
||||
11
log_test.go
11
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) {
|
||||
|
||||
119
logger.go
119
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)
|
||||
}
|
||||
|
||||
104
pool.go
104
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
|
||||
}
|
||||
|
||||
12
pool_test.go
12
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"
|
||||
})
|
||||
|
||||
163
standard.go
163
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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user