From a47c38631979d4ab4614af3c98ca2808255c0f3a Mon Sep 17 00:00:00 2001 From: LTbinglingfeng Date: Mon, 15 Jun 2026 15:51:15 +0800 Subject: [PATCH] Avoid counting all logs for tail reads --- internal/api/handlers/management/logs.go | 67 +------------------ internal/api/handlers/management/logs_test.go | 22 +++++- 2 files changed, 21 insertions(+), 68 deletions(-) diff --git a/internal/api/handlers/management/logs.go b/internal/api/handlers/management/logs.go index 3dfc635a..6d8477ba 100644 --- a/internal/api/handlers/management/logs.go +++ b/internal/api/handlers/management/logs.go @@ -101,12 +101,7 @@ func (h *Handler) GetLogs(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to read log files: %v", errTail)}) return } - total, errCount := countLogFileLines(files) - if errCount != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to read log files: %v", errCount)}) - return - } - writeLogsResponse(c, result.lines, total, result.latest, result.nextCursor, false) + writeLogsResponse(c, result.lines, len(result.lines), result.latest, result.nextCursor, false) return } @@ -612,66 +607,6 @@ func readTailLogLines(path string, limit int) (completeLogRead, error) { return readCompleteLogLines(path, start, boundary, limit) } -func countLogFileLines(files []string) (int, error) { - total := 0 - for i := range files { - count, errCount := countLogLines(files[i]) - if errCount != nil { - if errors.Is(errCount, os.ErrNotExist) { - continue - } - return 0, errCount - } - total += count - } - return total, nil -} - -func countLogLines(path string) (int, error) { - file, errOpen := os.Open(path) - if errOpen != nil { - return 0, errOpen - } - defer func() { - _ = file.Close() - }() - info, errStat := file.Stat() - if errStat != nil { - return 0, errStat - } - if info.IsDir() { - return 0, fmt.Errorf("invalid log file") - } - - buf := make([]byte, 32*1024) - count := 0 - lineLen := 0 - for { - n, errRead := file.Read(buf) - for _, b := range buf[:n] { - if b == '\n' { - count++ - lineLen = 0 - continue - } - lineLen++ - if lineLen > logScannerMaxBuffer { - return 0, fmt.Errorf("log line exceeds %d bytes", logScannerMaxBuffer) - } - } - if errRead == io.EOF { - break - } - if errRead != nil { - return 0, errRead - } - } - if lineLen > 0 { - count++ - } - return count, nil -} - func tailStartOffset(path string, boundary int64, limit int) (int64, error) { if limit <= 0 { return 0, nil diff --git a/internal/api/handlers/management/logs_test.go b/internal/api/handlers/management/logs_test.go index eb38176c..34a07c7e 100644 --- a/internal/api/handlers/management/logs_test.go +++ b/internal/api/handlers/management/logs_test.go @@ -149,8 +149,8 @@ func TestGetLogsTailLimitReturnsRecentLinesWithCursor(t *testing.T) { if !reflect.DeepEqual(resp.Lines, wantLines) { t.Fatalf("lines = %#v, want %#v", resp.Lines, wantLines) } - if resp.LineCount != 4 { - t.Fatalf("line-count = %d, want full scan count 4", resp.LineCount) + if resp.LineCount != len(wantLines) { + t.Fatalf("line-count = %d, want returned line count %d", resp.LineCount, len(wantLines)) } if resp.NextCursor == "" { t.Fatal("next-cursor is empty") @@ -161,6 +161,24 @@ func TestGetLogsTailLimitReturnsRecentLinesWithCursor(t *testing.T) { } } +func TestGetLogsTailLimitDoesNotScanOlderFilesForLineCount(t *testing.T) { + dir := t.TempDir() + rotatedPath := filepath.Join(dir, defaultLogFileName+".1") + if err := os.WriteFile(rotatedPath, []byte(strings.Repeat("x", logScannerMaxBuffer+1)+"\n"), 0o644); err != nil { + t.Fatalf("write rotated log: %v", err) + } + writeMainLog(t, dir, "[2026-06-15 10:00:00] current\n") + + resp := performGetLogs(t, newLogsTestHandler(dir, true), "/v0/management/logs?limit=1") + wantLines := []string{"[2026-06-15 10:00:00] current"} + if !reflect.DeepEqual(resp.Lines, wantLines) { + t.Fatalf("lines = %#v, want %#v", resp.Lines, wantLines) + } + if resp.LineCount != len(wantLines) { + t.Fatalf("line-count = %d, want returned line count %d", resp.LineCount, len(wantLines)) + } +} + func TestGetLogsNoLimitKeepsFullScanBehavior(t *testing.T) { dir := t.TempDir() writeMainLog(t, dir, "complete\npartial")