141 lines
3.7 KiB
Go
141 lines
3.7 KiB
Go
package fadada
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// debug 日志:将每次法大大 OpenAPI 调用的 cURL 命令 + 响应落盘到 logs/fadada/<date>/fadada.log
|
||
// 用于排查 /sign-task/create-with-template 等接口的实际请求/响应(如免验证签不生效)。
|
||
// 敏感字段(AppSecret)不会出现在日志里;AccessToken / Sign 会以明文写入以便复现。
|
||
|
||
const (
|
||
debugLogDir = "logs/fadada"
|
||
debugLogFile = "fadada.log"
|
||
debugMaxBytes = 10 * 1024 * 1024 // 单文件 10MB 后截断覆盖(轮转保持简单)
|
||
)
|
||
|
||
var (
|
||
debugLogMu sync.Mutex
|
||
debugLogFilePtr *os.File
|
||
debugLogDate string
|
||
debugLogSize int64
|
||
)
|
||
|
||
// writeDebugLog 把一次 API 调用的请求与响应追加到当日日志文件。
|
||
// 调用方需保证 headers / body / respBody 不包含 AppSecret(法大大签名里只有时间戳参与,secret 本身不出现在 header/body 中)。
|
||
func writeDebugLog(method, fullURL string, headers map[string]string, body string, httpStatus int, respBody string, requestID string) {
|
||
if strings.TrimSpace(fullURL) == "" {
|
||
return
|
||
}
|
||
|
||
entry := buildDebugEntry(method, fullURL, headers, body, httpStatus, respBody, requestID)
|
||
|
||
debugLogMu.Lock()
|
||
defer debugLogMu.Unlock()
|
||
|
||
if err := openDebugLogFileLocked(); err != nil {
|
||
// 日志失败不影响业务
|
||
return
|
||
}
|
||
|
||
n, _ := debugLogFilePtr.WriteString(entry)
|
||
debugLogSize += int64(n)
|
||
if debugLogSize >= debugMaxBytes {
|
||
_ = debugLogFilePtr.Close()
|
||
debugLogFilePtr = nil
|
||
debugLogSize = 0
|
||
}
|
||
}
|
||
|
||
func openDebugLogFileLocked() error {
|
||
today := time.Now().Format("2006-01-02")
|
||
if debugLogFilePtr != nil && debugLogDate == today {
|
||
return nil
|
||
}
|
||
if debugLogFilePtr != nil {
|
||
_ = debugLogFilePtr.Close()
|
||
}
|
||
dateDir := filepath.Join(debugLogDir, today)
|
||
if err := os.MkdirAll(dateDir, 0o755); err != nil {
|
||
return err
|
||
}
|
||
full := filepath.Join(dateDir, debugLogFile)
|
||
f, err := os.OpenFile(full, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if info, err := f.Stat(); err == nil {
|
||
debugLogSize = info.Size()
|
||
} else {
|
||
debugLogSize = 0
|
||
}
|
||
debugLogFilePtr = f
|
||
debugLogDate = today
|
||
return nil
|
||
}
|
||
|
||
// buildDebugEntry 生成一段可读的日志条目(cURL 命令 + 响应)。
|
||
func buildDebugEntry(method, fullURL string, headers map[string]string, body string, httpStatus int, respBody string, requestID string) string {
|
||
now := time.Now().Format("2006-01-02 15:04:05.000")
|
||
|
||
var sb strings.Builder
|
||
sb.WriteString("\n==================== ")
|
||
sb.WriteString(now)
|
||
sb.WriteString(" ====================\n")
|
||
|
||
// ---- cURL ----
|
||
sb.WriteString("curl -sS -X ")
|
||
sb.WriteString(strings.ToUpper(strings.TrimSpace(method)))
|
||
sb.WriteString(" '")
|
||
sb.WriteString(fullURL)
|
||
sb.WriteString("'")
|
||
for _, k := range sortedHeaderKeys(headers) {
|
||
sb.WriteString(" \\\n -H '")
|
||
sb.WriteString(k)
|
||
sb.WriteString(": ")
|
||
sb.WriteString(headers[k])
|
||
sb.WriteString("'")
|
||
}
|
||
if strings.TrimSpace(body) != "" {
|
||
sb.WriteString(" \\\n --data-urlencode 'bizContent=")
|
||
sb.WriteString(body)
|
||
sb.WriteString("'")
|
||
}
|
||
sb.WriteString("\n")
|
||
|
||
// ---- RESPONSE ----
|
||
sb.WriteString("--- RESPONSE ---\n")
|
||
sb.WriteString("HTTP ")
|
||
sb.WriteString(fmt.Sprintf("%d", httpStatus))
|
||
if requestID != "" {
|
||
sb.WriteString(" requestId=")
|
||
sb.WriteString(requestID)
|
||
}
|
||
sb.WriteString("\n")
|
||
sb.WriteString("body: ")
|
||
sb.WriteString(respBody)
|
||
sb.WriteString("\n")
|
||
return sb.String()
|
||
}
|
||
|
||
func sortedHeaderKeys(headers map[string]string) []string {
|
||
keys := make([]string, 0, len(headers))
|
||
for k := range headers {
|
||
keys = append(keys, k)
|
||
}
|
||
// 简单冒泡,依赖少
|
||
for i := 0; i < len(keys); i++ {
|
||
for j := i + 1; j < len(keys); j++ {
|
||
if keys[i] > keys[j] {
|
||
keys[i], keys[j] = keys[j], keys[i]
|
||
}
|
||
}
|
||
}
|
||
return keys
|
||
}
|