kra-new/internal/data/log_file.go

167 lines
4.5 KiB
Go

package data
import (
"bytes"
"context"
"errors"
"io/fs"
"os"
"path/filepath"
"sort"
"strings"
"time"
"kra/internal/biz"
)
func logRoot() (string, error) {
root, err := filepath.Abs("logs")
if err != nil {
return "", err
}
info, err := os.Stat(root)
if errors.Is(err, fs.ErrNotExist) {
return root, nil
}
if err != nil {
return "", err
}
if !info.IsDir() {
return "", errors.New("日志路径不是目录")
}
return root, nil
}
func (r *auditRepo) LogDates(ctx context.Context, month string) ([]biz.LogDate, error) {
if parsed, err := time.Parse("2006-01", month); err != nil || parsed.Format("2006-01") != month {
return nil, errors.New("日志月份格式不正确")
}
root, err := logRoot()
if err != nil {
return nil, err
}
entries, err := os.ReadDir(root)
if errors.Is(err, fs.ErrNotExist) {
return []biz.LogDate{}, nil
}
if err != nil {
return nil, err
}
out := []biz.LogDate{}
for _, entry := range entries {
if ctx.Err() != nil {
return nil, ctx.Err()
}
if entry.Type()&os.ModeSymlink != 0 || !entry.IsDir() || !strings.HasPrefix(entry.Name(), month+"-") {
continue
}
if parsed, parseErr := time.Parse("2006-01-02", entry.Name()); parseErr != nil || parsed.Format("2006-01-02") != entry.Name() {
continue
}
count := 0
_ = filepath.WalkDir(filepath.Join(root, entry.Name()), func(_ string, item fs.DirEntry, walkErr error) error {
if walkErr == nil && !item.IsDir() && strings.EqualFold(filepath.Ext(item.Name()), ".log") {
count++
}
return nil
})
if count > 0 {
out = append(out, biz.LogDate{Date: entry.Name(), FileCount: count})
}
}
sort.Slice(out, func(i, j int) bool { return out[i].Date < out[j].Date })
return out, nil
}
func (r *auditRepo) LogFiles(ctx context.Context, date string) ([]biz.LogFile, error) {
if parsed, err := time.Parse("2006-01-02", date); err != nil || parsed.Format("2006-01-02") != date {
return nil, errors.New("日志日期格式不正确")
}
root, err := logRoot()
if err != nil {
return nil, err
}
dateRoot := filepath.Join(root, date)
out := []biz.LogFile{}
err = filepath.WalkDir(dateRoot, func(path string, entry fs.DirEntry, walkErr error) error {
if walkErr != nil {
if errors.Is(walkErr, fs.ErrNotExist) {
return nil
}
return walkErr
}
if ctx.Err() != nil {
return ctx.Err()
}
if entry.Type()&os.ModeSymlink != 0 {
if entry.IsDir() {
return filepath.SkipDir
}
return nil
}
if entry.IsDir() || !strings.EqualFold(filepath.Ext(entry.Name()), ".log") {
return nil
}
info, infoErr := entry.Info()
if infoErr != nil {
return infoErr
}
relative, _ := filepath.Rel(dateRoot, path)
out = append(out, biz.LogFile{Path: filepath.ToSlash(relative), Name: entry.Name(), Size: info.Size(), ModifiedAt: info.ModTime()})
return nil
})
sort.Slice(out, func(i, j int) bool { return out[i].Path < out[j].Path })
return out, err
}
func (r *auditRepo) LogContent(ctx context.Context, date, path string, cursor *int64) (*biz.LogContent, error) {
if _, err := time.Parse("2006-01-02", date); err != nil {
return nil, errors.New("日志日期格式不正确")
}
root, err := logRoot()
if err != nil {
return nil, err
}
dateRoot := filepath.Join(root, date)
target := filepath.Clean(filepath.Join(dateRoot, filepath.FromSlash(path)))
if target == dateRoot || !strings.HasPrefix(target, dateRoot+string(os.PathSeparator)) || !strings.EqualFold(filepath.Ext(target), ".log") {
return nil, errors.New("日志文件路径不合法")
}
info, err := os.Lstat(target)
if err != nil {
return nil, err
}
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
return nil, errors.New("日志文件路径不合法")
}
end := info.Size()
if cursor != nil && *cursor >= 0 && *cursor < end {
end = *cursor
}
start := end - int64(2*1024*1024)
limited := start > 0
if start < 0 {
start = 0
}
file, err := os.Open(target)
if err != nil {
return nil, err
}
defer file.Close()
data := make([]byte, end-start)
n, err := file.ReadAt(data, start)
if err != nil && n == 0 {
return nil, err
}
data = data[:n]
lines := bytes.Split(data, []byte("\n"))
if len(lines) > 501 {
drop := len(lines) - 501
offset := 0
for _, line := range lines[:drop] {
offset += len(line) + 1
}
start += int64(offset)
data = data[offset:]
limited = true
}
return &biz.LogContent{Date: date, Path: path, Content: string(data), LineCount: bytes.Count(data, []byte("\n")), NextCursor: start, HasMore: start > 0, LimitedByBytes: limited, Size: info.Size(), ModifiedAt: info.ModTime()}, nil
}