Files
xk-hy-forward-go/internal/forward/testweb/apilog_list.go
2026-07-21 16:35:46 +08:00

126 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package testweb
import (
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
)
// 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$`)
// APILogEntry api-logs 中的日志条目。
type APILogEntry struct {
Rel string `json:"rel"`
Name string `json:"name"`
HourDir string `json:"hourDir"`
Kind string `json:"kind"`
Modified string `json:"modified"`
Size int64 `json:"size"`
}
// FileLogEntry 兼容旧 APIfile 通道)。
type FileLogEntry = APILogEntry
// ListAPILogs 列出 api-logskind 为空则 file+supervise否则 file 或 supervise。
func ListAPILogs(root, kind string) ([]APILogEntry, error) {
root, err := filepath.Abs(root)
if err != nil {
return nil, err
}
kind = strings.TrimSpace(strings.ToLower(kind))
var entries []APILogEntry
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()
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 {
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
}
entries = append(entries, APILogEntry{
Rel: rel,
Name: name,
HourDir: filepath.ToSlash(filepath.Dir(rel)),
Kind: entryKind,
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
}
// 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")
}
// 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
}