Files
xk-hy-forward-go/internal/forward/testweb/apilog_list.go

126 lines
3.0 KiB
Go
Raw Normal View History

2026-05-28 17:04:29 +08:00
package testweb
import (
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
)
2026-07-21 16:35:46 +08:00
// logRelRE 允许 file 与 supervise 通道 api-log 相对路径。
var logRelRE = regexp.MustCompile(`^\d{4}-\d{2}-\d{2} \d{2}/\d{8,14}_(file|supervise)_[a-f0-9]+\.log$`)
2026-05-28 17:04:29 +08:00
2026-07-21 16:35:46 +08:00
// APILogEntry api-logs 中的日志条目。
type APILogEntry struct {
2026-05-28 17:04:29 +08:00
Rel string `json:"rel"`
Name string `json:"name"`
HourDir string `json:"hourDir"`
2026-07-21 16:35:46 +08:00
Kind string `json:"kind"`
2026-05-28 17:04:29 +08:00
Modified string `json:"modified"`
Size int64 `json:"size"`
}
2026-07-21 16:35:46 +08:00
// FileLogEntry 兼容旧 APIfile 通道)。
type FileLogEntry = APILogEntry
// ListAPILogs 列出 api-logskind 为空则 file+supervise否则 file 或 supervise。
func ListAPILogs(root, kind string) ([]APILogEntry, error) {
2026-05-28 17:04:29 +08:00
root, err := filepath.Abs(root)
if err != nil {
return nil, err
}
2026-07-21 16:35:46 +08:00
kind = strings.TrimSpace(strings.ToLower(kind))
var entries []APILogEntry
2026-05-28 17:04:29 +08:00
err = filepath.WalkDir(root, func(path string, d os.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if d.IsDir() {
return nil
}
name := d.Name()
2026-07-21 16:35:46 +08:00
if !strings.HasSuffix(name, ".log") {
return nil
}
entryKind := ""
if strings.Contains(name, "_file_") {
entryKind = "file"
} else if strings.Contains(name, "_supervise_") {
entryKind = "supervise"
} else {
return nil
}
if kind != "" && entryKind != kind {
2026-05-28 17:04:29 +08:00
return nil
}
rel, err := filepath.Rel(root, path)
if err != nil {
return err
}
rel = filepath.ToSlash(rel)
if !logRelRE.MatchString(rel) {
return nil
}
info, err := d.Info()
if err != nil {
return err
}
2026-07-21 16:35:46 +08:00
entries = append(entries, APILogEntry{
2026-05-28 17:04:29 +08:00
Rel: rel,
Name: name,
2026-07-21 16:35:46 +08:00
HourDir: filepath.ToSlash(filepath.Dir(rel)),
Kind: entryKind,
2026-05-28 17:04:29 +08:00
Modified: info.ModTime().Format(time.RFC3339),
Size: info.Size(),
})
return nil
})
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].Modified > entries[j].Modified
})
return entries, nil
}
2026-07-21 16:35:46 +08:00
// ListFileLogs 列出 api-logs 下所有 *_file_*.log按修改时间倒序
func ListFileLogs(root string) ([]FileLogEntry, error) {
return ListAPILogs(root, "file")
}
// ListSuperviseLogs 列出 supervise 通道日志。
func ListSuperviseLogs(root string) ([]APILogEntry, error) {
return ListAPILogs(root, "supervise")
}
2026-05-28 17:04:29 +08:00
// resolveLogRelPath 校验 rel 并返回绝对路径。
func resolveLogRelPath(root, rel string) (string, error) {
rel = filepath.ToSlash(strings.TrimSpace(rel))
if rel == "" || strings.Contains(rel, "..") {
return "", fmt.Errorf("invalid rel path")
}
if !logRelRE.MatchString(rel) {
return "", fmt.Errorf("rel path not allowed")
}
root, err := filepath.Abs(root)
if err != nil {
return "", err
}
full := filepath.Join(root, filepath.FromSlash(rel))
full, err = filepath.Abs(full)
if err != nil {
return "", err
}
if !strings.HasPrefix(full, root+string(os.PathSeparator)) && full != root {
return "", fmt.Errorf("path escape")
}
return full, nil
}