u
This commit is contained in:
1 parent
2d036f4d95
commit
f373cf956d
22 files changed
+568
-280
No files matched your search
@@ -6,7 +6,6 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// String 类型字段
|
||||
func String(key, val string) zap.Field { return zap.String(key, val) }
|
||||
func Stringp(key string, val *string) zap.Field { return zap.Stringp(key, val) }
|
||||
func Strings(key string, val []string) zap.Field { return zap.Strings(key, val) }
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"base-framework/pkg/routinex"
|
||||
"base-framework/pkg/utils/strutil"
|
||||
"base-framework/pkg/utils/uid"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -15,10 +17,9 @@ import (
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// ----------------- 全局变量 -----------------
|
||||
var (
|
||||
Logger *zap.Logger
|
||||
|
||||
Logger *zap.Logger
|
||||
TrackID = "trackId"
|
||||
once sync.Once
|
||||
currentDate string
|
||||
mu sync.Mutex
|
||||
@@ -32,10 +33,8 @@ var (
|
||||
errorCore zapcore.Core
|
||||
)
|
||||
|
||||
const LogID = "log_id"
|
||||
const logTmFmtWithMS = "2006-01-02 15:04:05.000"
|
||||
|
||||
// 彩色日志级别
|
||||
var levelColor = map[zapcore.Level]string{
|
||||
zapcore.DebugLevel: "\033[36mDEBUG\033[0m",
|
||||
zapcore.InfoLevel: "\033[32mINFO\033[0m",
|
||||
@@ -47,7 +46,7 @@ var levelColor = map[zapcore.Level]string{
|
||||
}
|
||||
|
||||
// ----------------- trackCore -----------------
|
||||
// trackCore 包装 zapcore.Core,实现自动生成 track_id
|
||||
// 在写入日志时自动从 routinex 获取 log_id 并加入字段
|
||||
type trackCore struct {
|
||||
zapcore.Core
|
||||
}
|
||||
@@ -64,20 +63,27 @@ func (c *trackCore) Check(entry zapcore.Entry, ce *zapcore.CheckedEntry) *zapcor
|
||||
}
|
||||
|
||||
func (c *trackCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
|
||||
hasTrackID := false
|
||||
for _, f := range fields {
|
||||
if f.Key == LogID {
|
||||
hasTrackID = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasTrackID {
|
||||
fields = append(fields, Uint64(LogID, uid.NextID()))
|
||||
val := routinex.Get(TrackID)
|
||||
if val == nil {
|
||||
val = uid.NextID()
|
||||
routinex.Set(TrackID, val) // 保证同一请求复用同一 trackID
|
||||
}
|
||||
|
||||
fields = append(fields, zap.String(TrackID, strutil.ToString(val)))
|
||||
return c.Core.Write(entry, fields)
|
||||
}
|
||||
|
||||
// ----------------- 默认日志配置 -----------------
|
||||
// InitLogger ----------------- 初始化日志 -----------------
|
||||
func InitLogger() {
|
||||
once.Do(func() {
|
||||
if config.Log == nil {
|
||||
config.Log = defaultLogConfig()
|
||||
}
|
||||
createDirIfNotExist(config.Log.Dir)
|
||||
switchDate(time.Now().Format("2006-01-02"))
|
||||
})
|
||||
}
|
||||
|
||||
func defaultLogConfig() *types.Log {
|
||||
return &types.Log{
|
||||
Dir: "logs",
|
||||
@@ -91,21 +97,10 @@ func defaultLogConfig() *types.Log {
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------- 初始化 -----------------
|
||||
func InitLogger() {
|
||||
once.Do(func() {
|
||||
if config.Log == nil {
|
||||
config.Log = defaultLogConfig()
|
||||
}
|
||||
createDirIfNotExist(config.Log.Dir)
|
||||
switchDate(time.Now().Format("2006-01-02"))
|
||||
})
|
||||
}
|
||||
|
||||
// ----------------- 初始化核心 -----------------
|
||||
func switchDate(today string) {
|
||||
currentDate = today
|
||||
|
||||
// 关闭旧文件
|
||||
if infoFile != nil {
|
||||
_ = infoFile.Close()
|
||||
}
|
||||
@@ -116,7 +111,6 @@ func switchDate(today string) {
|
||||
_ = errorFile.Close()
|
||||
}
|
||||
|
||||
// 文件路径
|
||||
infoPath, warnPath, errorPath := config.Log.Dir, config.Log.Dir, config.Log.Dir
|
||||
if config.Log.LevelDirs {
|
||||
infoPath = filepath.Join(config.Log.Dir, "info")
|
||||
@@ -129,12 +123,10 @@ func switchDate(today string) {
|
||||
createDirIfNotExist(config.Log.Dir)
|
||||
}
|
||||
|
||||
// 打开文件
|
||||
infoFile = getOrCreateFile(filepath.Join(infoPath, fmt.Sprintf("info-%s.log", today)))
|
||||
warnFile = getOrCreateFile(filepath.Join(warnPath, fmt.Sprintf("warn-%s.log", today)))
|
||||
errorFile = getOrCreateFile(filepath.Join(errorPath, fmt.Sprintf("error-%s.log", today)))
|
||||
|
||||
// 文件编码器
|
||||
var fileEncoder zapcore.Encoder
|
||||
if config.Log.Format == "console" {
|
||||
fileEncoder = newConsoleEncoder(false)
|
||||
@@ -142,32 +134,29 @@ func switchDate(today string) {
|
||||
fileEncoder = newJSONEncoder()
|
||||
}
|
||||
|
||||
// 文件核心
|
||||
infoCore = zapcore.NewCore(fileEncoder, zapcore.AddSync(infoFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
infoCore = &trackCore{zapcore.NewCore(fileEncoder, zapcore.AddSync(infoFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl <= zapcore.InfoLevel
|
||||
}))
|
||||
warnCore = zapcore.NewCore(fileEncoder, zapcore.AddSync(warnFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
}))}
|
||||
|
||||
warnCore = &trackCore{zapcore.NewCore(fileEncoder, zapcore.AddSync(warnFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl == zapcore.WarnLevel
|
||||
}))
|
||||
errorCore = zapcore.NewCore(fileEncoder, zapcore.AddSync(errorFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
}))}
|
||||
|
||||
errorCore = &trackCore{zapcore.NewCore(fileEncoder, zapcore.AddSync(errorFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl >= zapcore.ErrorLevel
|
||||
}))
|
||||
}))}
|
||||
|
||||
cores := []zapcore.Core{
|
||||
&trackCore{infoCore},
|
||||
&trackCore{warnCore},
|
||||
&trackCore{errorCore},
|
||||
}
|
||||
cores := []zapcore.Core{infoCore, warnCore, errorCore}
|
||||
|
||||
// 开启 console 输出
|
||||
if config.Log.Console {
|
||||
consoleCore := zapcore.NewCore(newConsoleEncoder(true), zapcore.AddSync(os.Stdout), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
consoleCore := &trackCore{zapcore.NewCore(newConsoleEncoder(true), zapcore.AddSync(os.Stdout), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl >= zapcore.DebugLevel
|
||||
}))
|
||||
cores = append(cores, &trackCore{consoleCore})
|
||||
}))}
|
||||
cores = append(cores, consoleCore)
|
||||
}
|
||||
|
||||
Logger = zap.New(zapcore.NewTee(cores...))
|
||||
|
||||
if config.Log.Caller {
|
||||
Logger = Logger.WithOptions(zap.AddCaller())
|
||||
} else {
|
||||
@@ -182,7 +171,6 @@ func switchDate(today string) {
|
||||
}))
|
||||
}
|
||||
|
||||
// ----------------- 编码器 -----------------
|
||||
func newConsoleEncoder(enableColor bool) zapcore.Encoder {
|
||||
cfg := zapcore.EncoderConfig{
|
||||
TimeKey: "ts",
|
||||
@@ -206,7 +194,7 @@ func newConsoleEncoder(enableColor bool) zapcore.Encoder {
|
||||
},
|
||||
EncodeCaller: func(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) {
|
||||
pid := os.Getpid()
|
||||
enc.AppendString(fmt.Sprintf("[%d] %s", pid, caller.TrimmedPath()))
|
||||
enc.AppendString(fmt.Sprintf("[%d] [%d] %s", pid, routinex.CurGID(), caller.TrimmedPath()))
|
||||
},
|
||||
}
|
||||
return zapcore.NewConsoleEncoder(cfg)
|
||||
@@ -223,7 +211,6 @@ func newJSONEncoder() zapcore.Encoder {
|
||||
return zapcore.NewJSONEncoder(cfg)
|
||||
}
|
||||
|
||||
// ----------------- 工具函数 -----------------
|
||||
func checkDateSwitch() {
|
||||
today := time.Now().Format("2006-01-02")
|
||||
if today != currentDate {
|
||||
|
||||
@@ -6,13 +6,11 @@ import (
|
||||
"base-framework/pkg/router"
|
||||
"base-framework/pkg/utils/jwt"
|
||||
"errors"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Auth() router.HandlerFunc {
|
||||
return func(c *router.Context) {
|
||||
log.Println("[Auth] 开始鉴权")
|
||||
|
||||
tokenHeader := c.Header("Authorization")
|
||||
userIdHeader := c.Header("userId")
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"base-framework/pkg/logx"
|
||||
"base-framework/pkg/reponse"
|
||||
"base-framework/pkg/router"
|
||||
"base-framework/pkg/routinex"
|
||||
"base-framework/pkg/utils/strutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -34,15 +37,31 @@ func Error() router.HandlerFunc {
|
||||
lastErr := errs[len(errs)-1]
|
||||
stack := lastErr.StackTrace()
|
||||
location := extractBusinessLocation(stack)
|
||||
trackId := routinex.Get(logx.TrackID)
|
||||
|
||||
info := reponse.ErrorInfo{
|
||||
Method: c.Request.Method,
|
||||
Path: c.Request.URL.Path,
|
||||
Message: lastErr.Error(),
|
||||
Stack: stack,
|
||||
Location: location,
|
||||
var trackIDStr string
|
||||
if trackId != nil {
|
||||
trackIDStr = strutil.ToString(trackId) // 用你之前写的工具类
|
||||
}
|
||||
|
||||
var info = reponse.ErrorInfo{
|
||||
Method: c.Request.Method,
|
||||
Path: c.Request.URL.Path,
|
||||
Message: lastErr.Error(),
|
||||
//Stack: stack,
|
||||
Location: location,
|
||||
TrackId: trackIDStr,
|
||||
}
|
||||
|
||||
logx.Logger.Error(
|
||||
"系统异常",
|
||||
logx.String("trackID", trackIDStr),
|
||||
logx.String("method", c.Request.Method),
|
||||
logx.String("path", c.Request.URL.Path),
|
||||
logx.String("location", location),
|
||||
logx.String("stack", stack),
|
||||
)
|
||||
|
||||
reponse.Error(c).ErrorInfo(info).Send()
|
||||
c.Abort()
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"base-framework/pkg/logx"
|
||||
"base-framework/pkg/router"
|
||||
"time"
|
||||
"base-framework/pkg/routinex"
|
||||
"base-framework/pkg/utils/uid"
|
||||
)
|
||||
|
||||
func Logger() router.HandlerFunc {
|
||||
return func(c *router.Context) {
|
||||
start := time.Now()
|
||||
// 执行后续中间件和处理器
|
||||
routinex.Set(logx.TrackID, uid.NextID())
|
||||
|
||||
c.Next()
|
||||
elapsed := time.Since(start)
|
||||
// 即使业务中断,这里也能执行,打印耗时
|
||||
println("耗时:", elapsed.String())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"base-framework/pkg/router"
|
||||
"base-framework/pkg/routinex"
|
||||
)
|
||||
|
||||
func Routinex() router.HandlerFunc {
|
||||
return func(c *router.Context) {
|
||||
defer routinex.Clear()
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,11 @@ import (
|
||||
)
|
||||
|
||||
type ErrorInfo struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
TrackId string `json:"trackId,omitempty"`
|
||||
Method string `json:"method,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
Stack string `json:"stack,omitempty"`
|
||||
}
|
||||
|
||||
// Result 统一响应结构体
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"base-framework/pkg/utils"
|
||||
error2 "base-framework/pkg/utils/error"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
@@ -115,6 +115,7 @@ func (c *Context) Set(key string, value interface{}) {
|
||||
c.keys = make(map[string]interface{})
|
||||
}
|
||||
c.keys[key] = value
|
||||
|
||||
}
|
||||
|
||||
// Get 取值
|
||||
@@ -137,21 +138,21 @@ func (c *Context) AddError(err error) {
|
||||
}
|
||||
|
||||
if _, exists := c.keys["errors"]; !exists {
|
||||
c.keys["errors"] = []*utils.StackError{}
|
||||
c.keys["errors"] = []*error2.StackError{}
|
||||
}
|
||||
|
||||
// 统一生成堆栈
|
||||
se := utils.WrapWithStack(err)
|
||||
c.keys["errors"] = append(c.keys["errors"].([]*utils.StackError), se)
|
||||
se := error2.WrapWithStack(err)
|
||||
c.keys["errors"] = append(c.keys["errors"].([]*error2.StackError), se)
|
||||
}
|
||||
|
||||
// Errors 返回 []*utils.StackError
|
||||
func (c *Context) Errors() []*utils.StackError {
|
||||
func (c *Context) Errors() []*error2.StackError {
|
||||
if c.keys == nil {
|
||||
return nil
|
||||
}
|
||||
if errs, exists := c.keys["errors"]; exists {
|
||||
return errs.([]*utils.StackError)
|
||||
return errs.([]*error2.StackError)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package routinex
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ------------------- goroutine id -------------------
|
||||
func CurGID() uint64 {
|
||||
var buf [64]byte
|
||||
n := runtime.Stack(buf[:], false)
|
||||
line := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
|
||||
gid, _ := strconv.ParseUint(line, 10, 64)
|
||||
return gid
|
||||
}
|
||||
|
||||
type RoutineLocal struct {
|
||||
data sync.Map // gid -> map[key]any
|
||||
}
|
||||
|
||||
var (
|
||||
instance *RoutineLocal
|
||||
instanceOnce sync.Once
|
||||
)
|
||||
|
||||
func GetInstance() *RoutineLocal {
|
||||
instanceOnce.Do(func() {
|
||||
instance = &RoutineLocal{}
|
||||
})
|
||||
return instance
|
||||
}
|
||||
|
||||
// Set 存储单条数据
|
||||
func Set(key string, value any) {
|
||||
gid := CurGID()
|
||||
v, _ := GetInstance().data.LoadOrStore(gid, &sync.Map{})
|
||||
m := v.(*sync.Map)
|
||||
m.Store(key, value)
|
||||
}
|
||||
|
||||
// Get 获取单条数据
|
||||
func Get(key string) any {
|
||||
gid := CurGID()
|
||||
if v, ok := GetInstance().data.Load(gid); ok {
|
||||
m := v.(*sync.Map)
|
||||
if val, ok := m.Load(key); ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear 删除当前 goroutine 所有数据
|
||||
func Clear() {
|
||||
GetInstance().data.Delete(CurGID())
|
||||
}
|
||||
|
||||
// Go 封装函数,自动继承父 goroutine 数据
|
||||
func Go(f func()) {
|
||||
parentGID := CurGID()
|
||||
parentData, _ := GetInstance().data.Load(parentGID)
|
||||
|
||||
go func() {
|
||||
if parentData != nil {
|
||||
GetInstance().data.Store(CurGID(), parentData)
|
||||
defer Clear()
|
||||
}
|
||||
f()
|
||||
}()
|
||||
}
|
||||
@@ -1,48 +1,62 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (c *Client) QueryRows(query string, args ...any) ([]map[string]any, error) {
|
||||
func (c *Client) QueryRows(query string) ([]map[string]interface{}, error) {
|
||||
if c.Conn == nil {
|
||||
return nil, fmt.Errorf("database connection is nil")
|
||||
}
|
||||
|
||||
rows, err := c.Conn.Query(query, args...)
|
||||
rows, err := c.Conn.Query(query)
|
||||
if err != nil {
|
||||
return nil, err // 不加 debug.Stack()
|
||||
return nil, fmt.Errorf("query error: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func(rows *sql.Rows) {
|
||||
err := rows.Close()
|
||||
if err != nil {
|
||||
fmt.Println("关闭资源失败")
|
||||
}
|
||||
}(rows)
|
||||
|
||||
columns, err := rows.Columns()
|
||||
// 获取列名
|
||||
cols, err := rows.Columns()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := make([]map[string]any, 0)
|
||||
var result []map[string]interface{}
|
||||
|
||||
for rows.Next() {
|
||||
values := make([]any, len(columns))
|
||||
valuePtrs := make([]any, len(columns))
|
||||
for i := range columns {
|
||||
valuePtrs[i] = &values[i]
|
||||
// 创建扫描用的切片
|
||||
columnPointers := make([]interface{}, len(cols))
|
||||
columnValues := make([]interface{}, len(cols))
|
||||
for i := range columnPointers {
|
||||
columnPointers[i] = &columnValues[i]
|
||||
}
|
||||
|
||||
if err := rows.Scan(valuePtrs...); err != nil {
|
||||
if err := rows.Scan(columnPointers...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rowMap := make(map[string]any)
|
||||
for i, col := range columns {
|
||||
rowMap[col] = values[i]
|
||||
rowMap := make(map[string]interface{})
|
||||
for i, colName := range cols {
|
||||
val := columnValues[i]
|
||||
if b, ok := val.([]byte); ok {
|
||||
rowMap[colName] = string(b)
|
||||
} else {
|
||||
rowMap[colName] = val
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, rowMap)
|
||||
result = append(result, rowMap)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
return result, nil
|
||||
}
|
||||
+7
-3
@@ -1,6 +1,9 @@
|
||||
package utils
|
||||
package error
|
||||
|
||||
import "runtime/debug"
|
||||
import (
|
||||
"errors"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
// StackError 保存原始错误和堆栈
|
||||
type StackError struct {
|
||||
@@ -27,7 +30,8 @@ func WrapWithStack(err error) *StackError {
|
||||
return nil
|
||||
}
|
||||
|
||||
if se, ok := err.(*StackError); ok {
|
||||
var se *StackError
|
||||
if errors.As(err, &se) {
|
||||
return se
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ToString 通用高效转换为 string
|
||||
func ToString(v any) string {
|
||||
switch n := v.(type) {
|
||||
// 整数类型
|
||||
case int:
|
||||
return strconv.FormatInt(int64(n), 10)
|
||||
case int8:
|
||||
return strconv.FormatInt(int64(n), 10)
|
||||
case int16:
|
||||
return strconv.FormatInt(int64(n), 10)
|
||||
case int32:
|
||||
return strconv.FormatInt(int64(n), 10)
|
||||
case int64:
|
||||
return strconv.FormatInt(n, 10)
|
||||
|
||||
// 无符号整数
|
||||
case uint:
|
||||
return strconv.FormatUint(uint64(n), 10)
|
||||
case uint8:
|
||||
return strconv.FormatUint(uint64(n), 10)
|
||||
case uint16:
|
||||
return strconv.FormatUint(uint64(n), 10)
|
||||
case uint32:
|
||||
return strconv.FormatUint(uint64(n), 10)
|
||||
case uint64:
|
||||
return strconv.FormatUint(n, 10)
|
||||
|
||||
// 浮点数
|
||||
case float32:
|
||||
return strconv.FormatFloat(float64(n), 'f', -1, 32)
|
||||
case float64:
|
||||
return strconv.FormatFloat(n, 'f', -1, 64)
|
||||
|
||||
// 字符串
|
||||
case string:
|
||||
return n
|
||||
|
||||
// 布尔
|
||||
case bool:
|
||||
return strconv.FormatBool(n)
|
||||
|
||||
default:
|
||||
// fallback
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user