refactor: Optimize log file reading logic (#11064)

* refactor: Optimize log file reading with buffered reader pool and improve pagination logic

* refactor: Enhance file reading logic with improved buffer management and constant usage

* refactor: Adjust maximum read file size and enhance line reading functionality
This commit is contained in:
KOMATA 2025-11-25 22:42:29 +08:00 committed by GitHub
parent ac1f49f8dd
commit b416e6b6f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 149 additions and 72 deletions

View file

@ -5,10 +5,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/convert"
"github.com/1Panel-dev/1Panel/agent/utils/ini_conf"
"io" "io"
"io/fs" "io/fs"
"os" "os"
@ -21,6 +17,11 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/convert"
"github.com/1Panel-dev/1Panel/agent/utils/ini_conf"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"golang.org/x/text/encoding" "golang.org/x/text/encoding"
@ -591,7 +592,7 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
scope string scope string
logFileRes *dto.LogFileRes logFileRes *dto.LogFileRes
) )
if stat.Size() > 500*1024*1024 { if stat.Size() > files.MaxReadFileSize {
lines, err = files.TailFromEnd(logFilePath, req.PageSize) lines, err = files.TailFromEnd(logFilePath, req.PageSize)
isEndOfFile = true isEndOfFile = true
scope = "tail" scope = "tail"

View file

@ -3,6 +3,15 @@ package files
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"io"
"net/http"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/constant"
@ -14,13 +23,11 @@ import (
"golang.org/x/text/encoding/simplifiedchinese" "golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/encoding/traditionalchinese" "golang.org/x/text/encoding/traditionalchinese"
"golang.org/x/text/encoding/unicode" "golang.org/x/text/encoding/unicode"
"io" )
"net/http"
"os" const (
"os/user" MaxReadFileSize = 512 * 1024 * 1024
"path/filepath" tailBufSize = int64(32768)
"strconv"
"strings"
) )
func IsSymlink(mode os.FileMode) bool { func IsSymlink(mode os.FileMode) bool {
@ -78,27 +85,33 @@ func IsHidden(path string) bool {
return len(base) > 1 && base[0] == dotCharacter return len(base) > 1 && base[0] == dotCharacter
} }
func countLines(path string) (int, error) { var readerPool = sync.Pool{
file, err := os.Open(path) New: func() interface{} {
if err != nil { return bufio.NewReaderSize(nil, 8192)
return 0, err },
} }
defer file.Close()
reader := bufio.NewReader(file) var tailBufPool = sync.Pool{
count := 0 New: func() interface{} {
for { buf := make([]byte, tailBufSize)
_, err := reader.ReadString('\n') return &buf
if err != nil { },
}
func readLineTrimmed(reader *bufio.Reader) (string, error) {
line, err := reader.ReadString('\n')
if err == io.EOF { if err == io.EOF {
if count > 0 { if len(line) == 0 {
count++ return "", io.EOF
} }
return count, nil err = nil
} }
return count, err if err != nil {
} return "", err
count++
} }
line = strings.TrimSuffix(line, "\n")
line = strings.TrimSuffix(line, "\r")
return line, nil
} }
func TailFromEnd(filename string, lines int) ([]string, error) { func TailFromEnd(filename string, lines int) ([]string, error) {
@ -114,24 +127,26 @@ func TailFromEnd(filename string, lines int) ([]string, error) {
} }
fileSize := stat.Size() fileSize := stat.Size()
bufSize := int64(4096) bufPtr := tailBufPool.Get().(*[]byte)
buf := *bufPtr
defer tailBufPool.Put(bufPtr)
var result []string var result []string
var leftover string var leftover string
for offset := fileSize; offset > 0 && len(result) < lines; { for offset := fileSize; offset > 0 && len(result) < lines; {
readSize := bufSize readSize := tailBufSize
if offset < bufSize { if offset < tailBufSize {
readSize = offset readSize = offset
} }
offset -= readSize offset -= readSize
buf := make([]byte, readSize) _, err := file.ReadAt(buf[:readSize], offset)
_, err := file.ReadAt(buf, offset)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return nil, err return nil, err
} }
data := string(buf) + leftover data := string(buf[:readSize]) + leftover
linesInChunk := strings.Split(data, "\n") linesInChunk := strings.Split(data, "\n")
if offset > 0 { if offset > 0 {
@ -142,20 +157,28 @@ func TailFromEnd(filename string, lines int) ([]string, error) {
} }
for i := len(linesInChunk) - 1; i >= 0; i-- { for i := len(linesInChunk) - 1; i >= 0; i-- {
if len(result) < lines { if len(result) >= lines {
if !(i == len(linesInChunk)-1 && linesInChunk[i] == "" && len(result) == 0) { break
result = append([]string{linesInChunk[i]}, result...)
} }
if i == len(linesInChunk)-1 && linesInChunk[i] == "" && len(result) == 0 {
continue
} }
// 反插数据
result = append(result, linesInChunk[i])
} }
} }
if leftover != "" && len(result) < lines { if leftover != "" && len(result) < lines {
result = append([]string{leftover}, result...) result = append(result, leftover)
} }
if len(result) > lines { if len(result) > lines {
result = result[len(result)-lines:] result = result[:lines]
}
// 反转数据
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
result[i], result[j] = result[j], result[i]
} }
return result, nil return result, nil
@ -165,6 +188,10 @@ func ReadFileByLine(filename string, page, pageSize int, latest bool) (res *dto.
if !NewFileOp().Stat(filename) { if !NewFileOp().Stat(filename) {
return return
} }
if pageSize <= 0 {
err = fmt.Errorf("pageSize must be positive")
return
}
file, err := os.Open(filename) file, err := os.Open(filename)
if err != nil { if err != nil {
return return
@ -176,43 +203,92 @@ func ReadFileByLine(filename string, page, pageSize int, latest bool) (res *dto.
return return
} }
if fi.Size() > 500*1024*1024 { if fi.Size() > MaxReadFileSize {
err = buserr.New("ErrLogFileToLarge") err = buserr.New("ErrLogFileToLarge")
return return
} }
totalLines, err := countLines(filename) res = &dto.LogFileRes{}
if err != nil { reader := readerPool.Get().(*bufio.Reader)
reader.Reset(file)
defer readerPool.Put(reader)
if latest {
ringBuf := make([]string, pageSize)
writeIdx := 0
totalLines := 0
for {
line, readErr := readLineTrimmed(reader)
if readErr == io.EOF {
break
}
if readErr != nil {
err = readErr
return return
} }
res = &dto.LogFileRes{} ringBuf[writeIdx%pageSize] = line
writeIdx++
totalLines++
}
if totalLines == 0 {
res.Lines = []string{}
res.TotalLines = 0
res.TotalPages = 0
res.IsEndOfFile = true
return
}
total := (totalLines + pageSize - 1) / pageSize total := (totalLines + pageSize - 1) / pageSize
res.TotalPages = total res.TotalPages = total
res.TotalLines = totalLines res.TotalLines = totalLines
reader := bufio.NewReaderSize(file, 8192)
if latest { lastPageSize := totalLines % pageSize
page = total if lastPageSize == 0 {
lastPageSize = pageSize
} }
currentLine := 0 if lastPageSize > totalLines {
lastPageSize = totalLines
}
result := make([]string, 0, lastPageSize)
startIdx := writeIdx - lastPageSize
for i := 0; i < lastPageSize; i++ {
idx := (startIdx + i) % pageSize
result = append(result, ringBuf[idx])
}
res.Lines = result
res.IsEndOfFile = true
} else {
startLine := (page - 1) * pageSize startLine := (page - 1) * pageSize
endLine := startLine + pageSize endLine := startLine + pageSize
currentLine := 0
lines := make([]string, 0, pageSize) lines := make([]string, 0, pageSize)
for { for {
line, _, err := reader.ReadLine() line, readErr := readLineTrimmed(reader)
if err == io.EOF { if readErr == io.EOF {
break break
} }
if readErr != nil {
err = readErr
return
}
if currentLine >= startLine && currentLine < endLine { if currentLine >= startLine && currentLine < endLine {
lines = append(lines, string(line)) lines = append(lines, line)
} }
currentLine++ currentLine++
if currentLine >= endLine {
break
}
} }
res.Lines = lines res.Lines = lines
res.IsEndOfFile = currentLine < endLine res.TotalLines = currentLine
total := (currentLine + pageSize - 1) / pageSize
res.TotalPages = total
res.IsEndOfFile = page >= total
}
return return
} }