package openlog import ( "fmt" "log" "strings" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" ) const ( CtxReqID = "open_req_id" CtxDebug = "open_debug" CtxBody = "open_body" CtxStart = "open_start" CtxAPIKey = "open_api_key" CtxSide = "open_side" CtxAPIType = "open_api_type" CtxAction = "open_action" ) const ( SideClient = "client" SideSource = "source" APITypeClientV1 = "client_v1" APITypeSourceOpenV1 = "source_open_v1" APITypeUnknown = "unknown" ActionAuth = "auth" ActionList = "list" ActionCreate = "create" ActionQuery = "query" ActionCancel = "cancel" ActionWallet = "wallet" ActionPush = "push" ActionUnknown = "unknown" ) // Enabled 是否开启开放接口详细日志 var Enabled bool func Init(enabled bool) { Enabled = enabled if enabled { log.Printf("[open] 开放接口调试日志已开启 (OPEN_API_DEBUG)") } } func NewReqID() string { return strings.ReplaceAll(uuid.NewString(), "-", "")[:12] } func GetReqID(c *gin.Context) string { if v, ok := c.Get(CtxReqID); ok { if s, ok := v.(string); ok { return s } } return "-" } func EnsureReqID(c *gin.Context) string { if id := GetReqID(c); id != "-" { return id } id := NewReqID() c.Set(CtxReqID, id) return id } func SetScope(c *gin.Context, side, action string) { c.Set(CtxSide, side) c.Set(CtxAPIType, APITypeFromSide(side)) c.Set(CtxAction, action) } func SetAction(c *gin.Context, action string) { c.Set(CtxAction, action) } func ScopeFromPath(side, method, path string) (string, string) { action := ActionUnknown switch side { case SideClient: action = clientAction(method, path) case SideSource: action = sourceAction(method, path) } return side, action } func APITypeFromSide(side string) string { switch side { case SideClient: return APITypeClientV1 case SideSource: return APITypeSourceOpenV1 default: return APITypeUnknown } } func IsDebug(c *gin.Context) bool { if !Enabled { return false } if v, ok := c.Get(CtxDebug); ok { if b, ok := v.(bool); ok { return b } } return Enabled } // MaskKey 脱敏 api_key:保留前缀与后 4 位 func MaskKey(key string) string { if key == "" { return "(empty)" } if len(key) <= 8 { return key[:2] + "***" } return key[:8] + "***" + key[len(key)-4:] } // MaskSign 脱敏签名:只保留前 8 位 func MaskSign(sign string) string { if sign == "" { return "(empty)" } if len(sign) <= 8 { return sign + "..." } return sign[:8] + "..." } // Truncate 截断过长字符串 func Truncate(s string, max int) string { if max <= 0 || len(s) <= max { return s } return s[:max] + fmt.Sprintf("...(%d bytes)", len(s)) } func Prefix(c *gin.Context) string { side, _ := c.Get(CtxSide) apiType, _ := c.Get(CtxAPIType) action, _ := c.Get(CtxAction) sideText, _ := side.(string) apiTypeText, _ := apiType.(string) actionText, _ := action.(string) tag := "open" if apiTypeText != "" && apiTypeText != APITypeUnknown { tag += "." + apiTypeText } else if sideText != "" { tag += "." + sideText } if actionText != "" { tag += "." + actionText } fields := "" if apiTypeText != "" { fields += fmt.Sprintf(" api_type=%s", apiTypeText) } if actionText != "" { fields += fmt.Sprintf(" action=%s", actionText) } return fmt.Sprintf("[%s] req_id=%s%s ", tag, GetReqID(c), fields) } func Info(c *gin.Context, format string, args ...interface{}) { if !IsDebug(c) { return } log.Printf(Prefix(c)+format, args...) } func Warn(c *gin.Context, format string, args ...interface{}) { // 鉴权失败等也值得在 debug 时打出 if !Enabled { return } log.Printf(Prefix(c)+format, args...) } func Elapsed(c *gin.Context) time.Duration { if v, ok := c.Get(CtxStart); ok { if t, ok := v.(time.Time); ok { return time.Since(t) } } return 0 } func clientAction(method, path string) string { method = strings.ToUpper(method) switch { case method == "GET" && path == "/api/client/v1/products": return ActionList case method == "POST" && path == "/api/client/v1/orders": return ActionCreate case method == "GET" && strings.HasPrefix(path, "/api/client/v1/orders/") && strings.Contains(path, "/delivery"): return ActionQuery case method == "POST" && strings.HasPrefix(path, "/api/client/v1/orders/") && strings.Contains(path, "/delivery/bind"): return ActionCreate case method == "POST" && strings.HasPrefix(path, "/api/client/v1/orders/") && strings.Contains(path, "/delivery/submit"): return ActionCreate case method == "GET" && strings.HasPrefix(path, "/api/client/v1/orders/"): return ActionQuery case method == "POST" && strings.HasPrefix(path, "/api/client/v1/orders/") && strings.HasSuffix(path, "/cancel"): return ActionCancel case method == "GET" && path == "/api/client/v1/wallet": return ActionWallet default: return ActionUnknown } } func sourceAction(method, path string) string { method = strings.ToUpper(method) switch { case method == "GET" && strings.HasPrefix(path, "/api/open/v1/orders/"): return ActionQuery case method == "POST" && path == "/api/open/v1/orders/ship-notify": return ActionPush default: return ActionUnknown } }