diff --git a/go.mod b/go.mod index e2d934a..6aebdfa 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/go-sql-driver/mysql v1.8.1 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/jmoiron/sqlx v1.3.5 + github.com/sirupsen/logrus v1.9.3 ) require ( diff --git a/go.sum b/go.sum index ddaace4..82d6b69 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,8 @@ github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZ github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -90,6 +92,7 @@ golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+ golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..0abf9b9 --- /dev/null +++ b/readme.md @@ -0,0 +1,5 @@ + + + +# 记录需要修改的问题 +1. 邮件验证码传递密钥,验证码的传递 \ No newline at end of file diff --git a/server/app/internal/handlers/actions/auth.go b/server/app/internal/handlers/actions/auth.go index 155b498..ca1d8e2 100644 --- a/server/app/internal/handlers/actions/auth.go +++ b/server/app/internal/handlers/actions/auth.go @@ -1,12 +1,16 @@ package actions import ( - "log" - "github.com/gin-gonic/gin" + "github.com/iapologizewhenimwrong/Vestmore_GO/utils/auth" "github.com/iapologizewhenimwrong/Vestmore_GO/utils/basic" + "github.com/iapologizewhenimwrong/Vestmore_GO/utils/email" + "github.com/iapologizewhenimwrong/Vestmore_GO/utils/encryption_decryption" + "github.com/iapologizewhenimwrong/Vestmore_GO/utils/log" ) +var CompanyKey = "vestmore-bjwl" + // @Action base/getToken // Base_GetToken // action: string; @@ -58,6 +62,12 @@ func AccountForgetSmsCode(ctx *gin.Context, param *AccountForgetSmsCodeParam, re log.Println() } +type RegisterValidEmailCode struct { + Code string + Email string + AppMarket int +} + // @Action account/registerEmailCode // AccountRegisterEmailCode // randstr: string; @@ -68,7 +78,30 @@ func AccountForgetSmsCode(ctx *gin.Context, param *AccountForgetSmsCodeParam, re // timestamp: int64; // token: string; func AccountRegisterEmailCode(ctx *gin.Context, param *AccountRegisterEmailCodeParam, resp *basic.Response) { + log.Println(param) + if !email.IsEmailValid(param.Email) { + resp.Error(basic.ErrEmailFormat) + return + } + + gcm := encryption_decryption.NewSecretGCM[RegisterValidEmailCode](CompanyKey) + + code := auth.GenerateVerificationCode() + codetoken := &RegisterValidEmailCode{ + Code: code, + Email: param.Email, + AppMarket: param.AppMarket, + } + tokenstr, err := gcm.Encrypt(codetoken) + if err != nil { + resp.Error(basic.ErrEncGcm) + return + } + + resp.Success(map[string]any{ + "token": tokenstr, + }) } // @Action member/alterPassword diff --git a/server/app/internal/handlers/actions/types_gen.go b/server/app/internal/handlers/actions/types_gen.go index 5569c8d..3a1cac5 100644 --- a/server/app/internal/handlers/actions/types_gen.go +++ b/server/app/internal/handlers/actions/types_gen.go @@ -1,28 +1,27 @@ package actions import ( - "log" - "github.com/gin-gonic/gin" "github.com/iapologizewhenimwrong/Vestmore_GO/utils/basic" + "github.com/iapologizewhenimwrong/Vestmore_GO/utils/log" ) var HandlersFuncRoutes map[string]gin.HandlerFunc = make(map[string]gin.HandlerFunc) func init() { - // func AccountForgetSmsCode(ctx gin.Context, param AccountForgetSmsCodeParam, resp *basic.Response) + // func AccountForgetSmsCode(ctx *gin.Context, param *AccountForgetSmsCodeParam, resp *basic.Response) HandlersFuncRoutes["account/forgetSmsCode"] = AccountForgetSmsCodeHandler - // func AccountLoginWithEmailPassword(ctx gin.Context, param AccountLoginWithEmailPasswordParam, resp *basic.Response) + // func AccountLoginWithEmailPassword(ctx *gin.Context, param *AccountLoginWithEmailPasswordParam, resp *basic.Response) HandlersFuncRoutes["account/loginWithEmailPassword"] = AccountLoginWithEmailPasswordHandler - // func AccountLoginWithTelephonePassword(ctx gin.Context, param AccountLoginWithTelephonePasswordParam, resp *basic.Response) + // func AccountLoginWithTelephonePassword(ctx *gin.Context, param *AccountLoginWithTelephonePasswordParam, resp *basic.Response) HandlersFuncRoutes["account/loginWithTelephonePassword"] = AccountLoginWithTelephonePasswordHandler - // func AccountRegisterEmailCode(ctx gin.Context, param AccountRegisterEmailCodeParam, resp *basic.Response) + // func AccountRegisterEmailCode(ctx *gin.Context, param *AccountRegisterEmailCodeParam, resp *basic.Response) HandlersFuncRoutes["account/registerEmailCode"] = AccountRegisterEmailCodeHandler - // func AccountRegisterSmsCode(ctx gin.Context, param AccountRegisterSmsCodeParam, resp *basic.Response) + // func AccountRegisterSmsCode(ctx *gin.Context, param *AccountRegisterSmsCodeParam, resp *basic.Response) HandlersFuncRoutes["account/registerSmsCode"] = AccountRegisterSmsCodeHandler - // func BaseGetToken(ctx gin.Context, param BaseGetTokenParam, resp *basic.Response) + // func BaseGetToken(ctx *gin.Context, param *BaseGetTokenParam, resp *basic.Response) HandlersFuncRoutes["base/getToken"] = BaseGetTokenHandler - // func MemberAlterPassword(ctx gin.Context, param MemberAlterPasswordParam, resp *basic.Response) + // func MemberAlterPassword(ctx *gin.Context, param *MemberAlterPasswordParam, resp *basic.Response) HandlersFuncRoutes["member/alterPassword"] = MemberAlterPasswordHandler } @@ -34,7 +33,7 @@ type AccountForgetSmsCodeParam struct { } func AccountForgetSmsCodeHandler(ctx *gin.Context) { - resp := &basic.Response{} + resp := &basic.Response{IsSuccess: true} defer ctx.JSON(200, resp) param := &AccountForgetSmsCodeParam{} @@ -62,7 +61,7 @@ type AccountLoginWithEmailPasswordParam struct { } func AccountLoginWithEmailPasswordHandler(ctx *gin.Context) { - resp := &basic.Response{} + resp := &basic.Response{IsSuccess: true} defer ctx.JSON(200, resp) param := &AccountLoginWithEmailPasswordParam{} @@ -88,7 +87,7 @@ type AccountLoginWithTelephonePasswordParam struct { } func AccountLoginWithTelephonePasswordHandler(ctx *gin.Context) { - resp := &basic.Response{} + resp := &basic.Response{IsSuccess: true} defer ctx.JSON(200, resp) param := &AccountLoginWithTelephonePasswordParam{} @@ -113,7 +112,7 @@ type AccountRegisterEmailCodeParam struct { } func AccountRegisterEmailCodeHandler(ctx *gin.Context) { - resp := &basic.Response{} + resp := &basic.Response{IsSuccess: true} defer ctx.JSON(200, resp) param := &AccountRegisterEmailCodeParam{} @@ -135,7 +134,7 @@ type AccountRegisterSmsCodeParam struct { } func AccountRegisterSmsCodeHandler(ctx *gin.Context) { - resp := &basic.Response{} + resp := &basic.Response{IsSuccess: true} defer ctx.JSON(200, resp) param := &AccountRegisterSmsCodeParam{} @@ -157,7 +156,7 @@ type BaseGetTokenParam struct { } func BaseGetTokenHandler(ctx *gin.Context) { - resp := &basic.Response{} + resp := &basic.Response{IsSuccess: true} defer ctx.JSON(200, resp) param := &BaseGetTokenParam{} @@ -180,7 +179,7 @@ type MemberAlterPasswordParam struct { } func MemberAlterPasswordHandler(ctx *gin.Context) { - resp := &basic.Response{} + resp := &basic.Response{IsSuccess: true} defer ctx.JSON(200, resp) param := &MemberAlterPasswordParam{} diff --git a/server/app/internal/handlers/gen_action_routes.tpl b/server/app/internal/handlers/gen_action_routes.tpl deleted file mode 100644 index d2e1d98..0000000 --- a/server/app/internal/handlers/gen_action_routes.tpl +++ /dev/null @@ -1,2 +0,0 @@ -package handlers - diff --git a/server/app/internal/handlers/types_gen.tpl b/server/app/internal/handlers/types_gen.tpl index d940747..f30ba10 100644 --- a/server/app/internal/handlers/types_gen.tpl +++ b/server/app/internal/handlers/types_gen.tpl @@ -3,6 +3,7 @@ package actions import ( "github.com/gin-gonic/gin" "github.com/iapologizewhenimwrong/Vestmore_GO/utils/basic" + "github.com/iapologizewhenimwrong/Vestmore_GO/utils/log" ) var HandlersFuncRoutes map[string]gin.HandlerFunc = make(map[string]gin.HandlerFunc) @@ -22,7 +23,7 @@ type {{.ParamStruct.ParamStructName}} struct { } func {{.FuncName}}Handler(ctx *gin.Context) { - resp := &basic.Response{} + resp := &basic.Response{IsSuccess: true} defer ctx.JSON(200, resp) param := &{{.ParamStruct.ParamStructName}}{} diff --git a/utils/auth/valid_code.go b/utils/auth/valid_code.go new file mode 100644 index 0000000..5e40775 --- /dev/null +++ b/utils/auth/valid_code.go @@ -0,0 +1,21 @@ +package auth + +import ( + "fmt" + "math/rand" +) + +func GenerateVerificationCode() string { + var code string + + for i := 0; i < 3; i++ { + // 生成两个 10-99 之间的随机数 + a := rand.Intn(90-10+1) + 10 + b := rand.Intn(90-10+1) + 10 + c := rand.Intn(90-10+1) + 10 + // 将随机数拼接到验证码字符串 + code += fmt.Sprintf("%d%d%d", a, b, c) + } + + return code +} diff --git a/utils/basic/error_code.go b/utils/basic/error_code.go index 7110571..11abaa1 100644 --- a/utils/basic/error_code.go +++ b/utils/basic/error_code.go @@ -6,5 +6,9 @@ type ErrorCode struct { } var ( + ErrEncGcm = &ErrorCode{Code: 10001, Message: "gmc加密错误"} + ErrParamParse = &ErrorCode{Code: 10100, Message: "参数解析错误"} + + ErrEmailFormat = &ErrorCode{Code: 10101, Message: "email 格式错误"} ) diff --git a/utils/basic/types.go b/utils/basic/types.go index c151874..91d7903 100644 --- a/utils/basic/types.go +++ b/utils/basic/types.go @@ -34,7 +34,7 @@ func (resp *Response) setData(Data []interface{}) { } if len(Data) == 1 { - resp.Data = Data + resp.Data = Data[0] } else { resp.Data = Data } diff --git a/utils/cors/cors.go b/utils/cors/cors.go index 5576acd..2679b0a 100644 --- a/utils/cors/cors.go +++ b/utils/cors/cors.go @@ -11,7 +11,7 @@ func SetCors(router *gin.Engine) { router.OPTIONS("/*path", func(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") // 可以根据实际情况改为特定域名 c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") - c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization") + c.Header("Access-Control-Allow-Headers", "*") c.Header("Access-Control-Allow-Credentials", "true") c.Status(http.StatusOK) }) @@ -20,7 +20,7 @@ func SetCors(router *gin.Engine) { router.Use(func(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") // 允许所有域名跨域访问,也可以指定特定的域名 c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") - c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization") + c.Header("Access-Control-Allow-Headers", "*") c.Header("Access-Control-Allow-Credentials", "true") c.Next() }) diff --git a/utils/email/verify.go b/utils/email/verify.go new file mode 100644 index 0000000..f389a84 --- /dev/null +++ b/utils/email/verify.go @@ -0,0 +1,15 @@ +package email + +import "regexp" + +// 验证是否邮箱 +func IsEmailValid(email string) bool { + // 邮箱正则表达式 + regex := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` + + // 编译正则表达式 + regexPattern := regexp.MustCompile(regex) + + // 根据正则表达式验证邮箱 + return regexPattern.MatchString(email) +} diff --git a/utils/log/json_format.go b/utils/log/json_format.go new file mode 100644 index 0000000..beeb27b --- /dev/null +++ b/utils/log/json_format.go @@ -0,0 +1,85 @@ +package log + +import ( + "bytes" + "encoding/json" + "fmt" + "runtime" + "strings" + "sync" + "time" + + "github.com/sirupsen/logrus" +) + +type levelSkip struct { + Skip int + Once sync.Once +} + +// JSONFormatter formats logs into parsable json +type JSONFormatter struct { + skip []*levelSkip + + once sync.Once +} + +// Format renders a single log entry +func (h *JSONFormatter) Format(e *logrus.Entry) ([]byte, error) { + + skipOnce := h.skip[int(e.Level)] + skipOnce.Once.Do(func() { + for i := 4; i < 100; i++ { + // log.Println(i) + if pc, _, _, ok := runtime.Caller(i); ok { + funcStruct := runtime.FuncForPC(pc) + // log.Println(funcStruct.Name(), file, line) + if !strings.Contains(funcStruct.Name(), "github.com/sirupsen/logrus.") { + skipOnce.Skip++ + if skipOnce.Skip >= 2 { + skipOnce.Skip = i - 3 + break + } + } + } else { + break + } + + } + }) + + var fileinfo string + if _, file, line, ok := runtime.Caller(skipOnce.Skip); ok { + if e.Level == logrus.InfoLevel { + fileinfo = fmt.Sprintf("%s:%d", file, line) + } else { + ps := strings.Split(file, "/") + ps = ps[len(ps)-4:] + fileinfo = fmt.Sprintf("%s:%d", strings.Join(ps, "/"), line) + } + + } + + var Data map[string]any = make(map[string]any, 4) + + Data[logrus.FieldKeyTime] = e.Time.Format(time.RFC3339) + Data[logrus.FieldKeyMsg] = e.Message + Data[logrus.FieldKeyLevel] = e.Level + Data[logrus.FieldKeyFile] = fileinfo + + var b *bytes.Buffer + if e.Buffer != nil { + b = e.Buffer + } else { + b = &bytes.Buffer{} + } + + encoder := json.NewEncoder(b) + encoder.SetEscapeHTML(false) + + if err := encoder.Encode(Data); err != nil { + return nil, fmt.Errorf("failed to marshal fields to JSON, %w", err) + } + + return b.Bytes(), nil +} diff --git a/utils/log/log.go b/utils/log/log.go new file mode 100644 index 0000000..eb29561 --- /dev/null +++ b/utils/log/log.go @@ -0,0 +1,252 @@ +package log + +import ( + "context" + "fmt" + "log" + "runtime" + "strings" + "sync" + "time" + + "github.com/sirupsen/logrus" +) + +var l *logrus.Logger + +func init() { + l = logrus.New() + + // 配置 Logstash 作为输出 + + l.AddHook(NewUTCTimeHook()) + jf := &JSONFormatter{ + skip: make([]*levelSkip, len(logrus.AllLevels)), + } + + for i := range jf.skip { + jf.skip[i] = &levelSkip{} + } + + l.Formatter = jf + + // l.AddHook(&SkipHook{}) + + l.SetReportCaller(true) + +} + +type SkipHook struct { + autoSkip int + Formatter func(*logrus.Hook, *logrus.Entry) error + once sync.Once +} + +func (h *SkipHook) Levels() []logrus.Level { + return logrus.AllLevels +} + +func (h *SkipHook) Fire(e *logrus.Entry) error { + h.once.Do(func() { + for i := 4; i < 100; i++ { + log.Println(i) + if pc, file, line, ok := runtime.Caller(i); ok { + funcStruct := runtime.FuncForPC(pc) + log.Println(funcStruct.Name(), file, line) + if !strings.Contains(funcStruct.Name(), "github.com/sirupsen/logrus.") { + h.autoSkip++ + if h.autoSkip >= 2 { + h.autoSkip = i - 3 + break + } + } + } else { + break + } + + } + }) + + if _, file, line, ok := runtime.Caller(h.autoSkip); ok { + // funcStruct := runtime.FuncForPC(pc) + // log.Println(file, line, funcStruct.Name()) + // funcName := funcStruct.Name() + file := fmt.Sprintf("%s:%d", file, line) + e.Data["file"] = file + } + + return nil +} + +// 自定义钩子以设置时间为UTC +type UTCTimeHook struct{} + +func NewUTCTimeHook() *UTCTimeHook { + return &UTCTimeHook{} +} + +func (hook *UTCTimeHook) Levels() []logrus.Level { + return logrus.AllLevels +} + +func (hook *UTCTimeHook) Fire(entry *logrus.Entry) error { + entry.Time = time.Now().UTC() + return nil +} + +// WithField allocates a new entry and adds a field to it. +// Debug, Print, Info, Warn, Error, Fatal or Panic must be then applied to +// this new returned entry. +// If you want multiple fields, use `WithFields`. +func WithField(key string, value interface{}) *logrus.Entry { + return l.WithField(key, value) +} + +// Adds a struct of fields to the log entry. All it does is call `WithField` for +// each `Field`. +func WithFields(fields logrus.Fields) *logrus.Entry { + return l.WithFields(fields) +} + +// Add an error as single field to the log entry. All it does is call +// `WithError` for the given `error`. +func WithError(err error) *logrus.Entry { + + return l.WithError(err) +} + +// Add a context to the log entry. +func WithContext(ctx context.Context) *logrus.Entry { + + return l.WithContext(ctx) +} + +// Overrides the time of the log entry. +func WithTime(t time.Time) *logrus.Entry { + + return l.WithTime(t) +} + +func Logf(level logrus.Level, format string, args ...interface{}) { + l.Logf(level, format, args...) +} + +func Tracef(format string, args ...interface{}) { + l.Tracef(format, args...) +} + +func Debugf(format string, args ...interface{}) { + l.Debugf(format, args...) +} + +func Infof(format string, args ...interface{}) { + l.Infof(format, args...) +} + +func Printf(format string, args ...interface{}) { + l.Printf(format, args...) +} + +func Warnf(format string, args ...interface{}) { + l.Warnf(format, args...) +} + +func Warningf(format string, args ...interface{}) { + l.Warningf(format, args...) +} + +func Errorf(format string, args ...interface{}) { + l.Errorf(format, args...) +} + +func Fatalf(format string, args ...interface{}) { + l.Fatalf(format, args...) +} + +func Panicf(format string, args ...interface{}) { + l.Panicf(format, args...) +} + +func Log(level logrus.Level, args ...interface{}) { + l.Log(level, args...) +} + +func Trace(args ...interface{}) { + l.Trace(args...) +} + +func Debug(args ...interface{}) { + l.Debug(args...) +} + +func Info(args ...interface{}) { + l.Info(args...) +} + +func Print(args ...interface{}) { + l.Print(args...) +} + +func Warn(args ...interface{}) { + l.Warn(args...) +} + +func Warning(args ...interface{}) { + l.Warning(args...) +} + +func Error(args ...interface{}) { + l.Error(args...) +} + +func Fatal(args ...interface{}) { + l.Fatal(args...) +} + +func Panic(args ...interface{}) { + l.Panic(args...) +} + +func Logln(level logrus.Level, args ...interface{}) { + l.Logln(level, args...) +} + +func Traceln(args ...interface{}) { + l.Traceln(args...) +} + +func Debugln(args ...interface{}) { + l.Debugln(args...) +} + +func Infoln(args ...interface{}) { + l.Infoln(args...) +} + +func Println(args ...interface{}) { + l.Println(args...) +} + +func Warnln(args ...interface{}) { + l.Warnln(args...) +} + +func Warningln(args ...interface{}) { + l.Warningln(args...) +} + +func Errorln(args ...interface{}) { + l.Errorln(args...) +} + +func Fatalln(args ...interface{}) { + l.Fatalln(args...) +} + +func Panicln(args ...interface{}) { + l.Panicln(args...) +} + +func Exit(code int) { + l.Exit(code) +} diff --git a/utils/log/log_time.go b/utils/log/log_time.go new file mode 100644 index 0000000..32a13c9 --- /dev/null +++ b/utils/log/log_time.go @@ -0,0 +1,28 @@ +package log + +import ( + "fmt" + "time" +) + +// 跟踪时间 + +func DebuglnTrackTime(do func(), fargs ...interface{}) { + now := time.Now() + do() + var t interface{} = fmt.Sprintf("[%dms]", time.Since(now).Milliseconds()) + var out []interface{} + out = append(out, t) + out = append(out, fargs...) + l.Debugln(out...) +} + +func InfolnTrackTime(do func(), fargs ...interface{}) { + now := time.Now() + do() + var t interface{} = fmt.Sprintf("[%dms]", time.Since(now).Milliseconds()) + var out []interface{} + out = append(out, t) + out = append(out, fargs...) + l.Infoln(out...) +} diff --git a/utils/log/readme.md b/utils/log/readme.md new file mode 100644 index 0000000..b65a3d9 --- /dev/null +++ b/utils/log/readme.md @@ -0,0 +1,74 @@ +# Log Package README + +## 概述 +此`log`包提供了一套全面的日志记录功能,基于`github.com/sirupsen/logrus`库进行了深度定制和扩展。通过本包,您可以实现多级别的日志输出,并能灵活控制日志格式、时间戳显示以及调用栈信息等。 +用于fusen的全部服务的统一格式化, 便于运维统一处理。 + +### 功能特性 +1. **多级别日志**:支持`Trace`、`Debug`、`Info`、`Warn`、`Error`、`Fatal`和`Panic`等多种日志级别。 + +2. **追踪时间**:包含`DebuglnTrackTime`与`InfolnTrackTime`函数,用于执行指定函数并在输出时附加执行耗时(毫秒)。 + +3. **UTC时间**:使用了自定义的`UTCTimeHook`钩子,确保所有日志的时间戳采用协调世界时(UTC)格式。 + +4. **JSON格式化**:实现了`JSONFormatter`结构体,将日志条目格式化为可解析的JSON字符串。它自动跳过与logrus库自身相关的调用层级,以便更准确地显示应用代码中的文件和行号信息。 + +5. **上下文关联**:提供了`WithContext`方法,允许在日志条目中携带Go context.Context,便于进行请求级或事务级的跟踪。 + +6. **错误处理**:通过`WithError`方法可以在日志条目中便捷地添加错误对象。 + +7. **时间戳覆盖**:`WithTime`方法允许开发者为日志条目设置特定的时间戳。 + +8. **灵活格式化**:除了基本的日志输出外,还支持带有格式字符串的函数如`Printf`、`Debugf`等,以适应不同的消息格式需求。 + +9. **源码定位**:已启用报告调用者位置的功能,每条日志都会附带其产生的源代码文件名及行号。 + +### 使用示例 + +```go +package main + +import ( + "github.com/yourorg/log" // 替换为实际导入路径 + "time" +) + +func main() { + log.Debugln("This is a debug message.") + log.Infoln("Normal info message.") + + // 添加自定义字段并格式化输出 + entry := log.WithField("user_id", "123") + entry.Info("User logged in.") + + // 记录并追踪函数执行时间 + log.DebuglnTrackTime(func() { + time.Sleep(200 * time.Millisecond) + }, "Function execution") + + // 设置日志上下文 + ctx := context.Background() + ctx = log.WithContext(ctx, "request_id", "abc123") + log.Infof(ctx, "Received request with ID: %s", "abc123") + + // 输出JSON格式化的日志 + log.SetFormatter(&log.JSONFormatter{}) + log.Warn("JSON-formatted warning.") +} +``` + +### 配置选项 +- 在初始化阶段,已经配置了Logstash作为日志输出的目标。 +- 可通过修改`l.Formatter`属性来更换或自定义日志格式器。 +- 通过`AddHook`方法可以添加自定义的钩子,比如这里有一个未使用的`SkipHook`,它可以用来过滤调用栈。 + +### 注意事项 +- 当需要同时记录多个字段时,请使用`WithFields`方法而不是多次调用`WithField`。 +- 若要调整日志输出级别,请直接操作全局logger实例的level设置。 + +### 结构体与方法详解 +- `levelSkip` 结构体用于在堆栈跟踪过程中计算需要跳过的层级数。 +- `UTCTimeHook` 是一个自定义钩子,当触发时会将日志条目的时间戳设置为当前UTC时间。 +- `JSONFormatter` 实现了`Format`方法,将日志条目转化为JSON格式。 + + \ No newline at end of file diff --git a/utils/log/tx/log_test.go b/utils/log/tx/log_test.go new file mode 100644 index 0000000..fade4df --- /dev/null +++ b/utils/log/tx/log_test.go @@ -0,0 +1 @@ +package log_test