Skip to content

Commit 01a55c2

Browse files
authored
Merge pull request #15 from tom-draper/fix/rotated-log-files
fix(#14): handle uncompressed rotated log files
2 parents 2fb1e03 + 6fd905c commit 01a55c2

4 files changed

Lines changed: 240 additions & 138 deletions

File tree

agent/pkg/logs/logs.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os"
88
"path/filepath"
99
"sort"
10+
"strconv"
1011
"strings"
1112
"sync"
1213

@@ -23,6 +24,22 @@ type LogResult struct {
2324
Positions []Position `json:"positions,omitempty"`
2425
}
2526

27+
func isNumericExtension(ext string) bool {
28+
if len(ext) < 2 || ext[0] != '.' {
29+
return false
30+
}
31+
_, err := strconv.Atoi(ext[1:])
32+
return err == nil
33+
}
34+
35+
func isRotatedLogFile(name string) bool {
36+
ext := filepath.Ext(name)
37+
if !isNumericExtension(ext) {
38+
return false
39+
}
40+
return strings.HasSuffix(strings.TrimSuffix(name, ext), ".log")
41+
}
42+
2643
func GetLogs(path string, positions []Position, isErrorLog bool, includeCompressed bool) (LogResult, error) {
2744
// Check if we're serving a directory or a single file
2845
fileInfo, err := os.Stat(path)
@@ -157,13 +174,17 @@ func GetDirectoryLogs(dirPath string, positions []Position, isErrorLog bool, inc
157174
// Filter log files
158175
var logFiles []string
159176
for _, entry := range entries {
177+
if entry.IsDir() {
178+
continue
179+
}
160180
fileName := entry.Name()
161181
// Check if it's a log file
162182
isLogFile := strings.HasSuffix(fileName, ".log")
163183
isGzipFile := strings.HasSuffix(fileName, ".gz")
184+
isRotated := isRotatedLogFile(fileName)
164185

165186
// Filter by log type and extension
166-
if (isLogFile || (isGzipFile && includeCompressed)) &&
187+
if (isLogFile || isRotated || (isGzipFile && includeCompressed)) &&
167188
(isErrorLog && strings.Contains(fileName, "error") || !isErrorLog && !strings.Contains(fileName, "error")) {
168189
logFiles = append(logFiles, fileName)
169190
}
@@ -214,7 +235,7 @@ func GetDirectoryLogs(dirPath string, positions []Position, isErrorLog bool, inc
214235
}
215236

216237
r := fileResult{logs: result.Logs}
217-
if strings.HasSuffix(fp.Filename, ".log") {
238+
if !strings.HasSuffix(fp.Filename, ".gz") {
218239
var pos int64
219240
if len(result.Positions) > 0 {
220241
pos = result.Positions[0].Position

agent/pkg/logs/logs_test.go

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,135 @@
1-
package logs
1+
package logs
2+
3+
import (
4+
"compress/gzip"
5+
"os"
6+
"path/filepath"
7+
"slices"
8+
"testing"
9+
)
10+
11+
func TestGetDirectoryLogs_RotatedFiles(t *testing.T) {
12+
// Setup test directory
13+
dirPath, err := os.MkdirTemp("", "test_logs")
14+
if err != nil {
15+
t.Fatalf("Failed to create temp dir: %v", err)
16+
}
17+
defer os.RemoveAll(dirPath)
18+
19+
// Create test log files
20+
files := map[string]string{
21+
"access.log": "today logs\n",
22+
"access.log.1": "yesterday logs\n",
23+
"access.log.2.gz": "older logs\n",
24+
"error.log": "error logs\n",
25+
"notes.1": "not a log\n",
26+
}
27+
28+
for name, content := range files {
29+
fullPath := filepath.Join(dirPath, name)
30+
if filepath.Ext(name) == ".gz" {
31+
f, err := os.Create(fullPath)
32+
if err != nil {
33+
t.Fatalf("Failed to create gzip file %s: %v", name, err)
34+
}
35+
gw := gzip.NewWriter(f)
36+
if _, err := gw.Write([]byte(content)); err != nil {
37+
t.Fatalf("Failed to write gzip file %s: %v", name, err)
38+
}
39+
if err := gw.Close(); err != nil {
40+
t.Fatalf("Failed to close gzip writer for %s: %v", name, err)
41+
}
42+
if err := f.Close(); err != nil {
43+
t.Fatalf("Failed to close gzip file %s: %v", name, err)
44+
}
45+
} else {
46+
err := os.WriteFile(fullPath, []byte(content), 0644)
47+
if err != nil {
48+
t.Fatalf("Failed to create test file %s: %v", name, err)
49+
}
50+
}
51+
}
52+
53+
tests := []struct {
54+
name string
55+
isErrorLog bool
56+
includeCompressed bool
57+
wantLogs []string
58+
wantPositions []string
59+
}{
60+
{
61+
name: "Access logs - no compressed",
62+
isErrorLog: false,
63+
includeCompressed: false,
64+
wantLogs: []string{"today logs", "yesterday logs"},
65+
wantPositions: []string{"access.log", "access.log.1"},
66+
},
67+
{
68+
name: "Access logs - with compressed",
69+
isErrorLog: false,
70+
includeCompressed: true,
71+
wantLogs: []string{"today logs", "yesterday logs", "older logs"},
72+
wantPositions: []string{"access.log", "access.log.1"},
73+
},
74+
{
75+
name: "Error logs",
76+
isErrorLog: true,
77+
includeCompressed: false,
78+
wantLogs: []string{"error logs"},
79+
wantPositions: []string{"error.log"},
80+
},
81+
}
82+
83+
for _, tt := range tests {
84+
t.Run(tt.name, func(t *testing.T) {
85+
result, err := GetDirectoryLogs(dirPath, nil, tt.isErrorLog, tt.includeCompressed)
86+
if err != nil {
87+
t.Fatalf("GetDirectoryLogs failed: %v", err)
88+
}
89+
90+
if !slices.Equal(result.Logs, tt.wantLogs) {
91+
t.Fatalf("unexpected logs: got %v want %v", result.Logs, tt.wantLogs)
92+
}
93+
94+
var gotPositions []string
95+
for _, pos := range result.Positions {
96+
gotPositions = append(gotPositions, pos.Filename)
97+
}
98+
if !slices.Equal(gotPositions, tt.wantPositions) {
99+
t.Fatalf("unexpected positions: got %v want %v", gotPositions, tt.wantPositions)
100+
}
101+
102+
for _, unexpected := range []string{"not a log", "error logs", "older logs"} {
103+
if tt.isErrorLog || tt.includeCompressed {
104+
if unexpected == "error logs" || unexpected == "older logs" {
105+
continue
106+
}
107+
}
108+
if slices.Contains(result.Logs, unexpected) {
109+
t.Errorf("unexpected log line %q was returned", unexpected)
110+
}
111+
}
112+
})
113+
}
114+
}
115+
116+
func TestIsNumericExtension(t *testing.T) {
117+
tests := []struct {
118+
ext string
119+
expected bool
120+
}{
121+
{".1", true},
122+
{".10", true},
123+
{".log", false},
124+
{".gz", false},
125+
{".", false},
126+
{"", false},
127+
{".1a", false},
128+
}
129+
130+
for _, tt := range tests {
131+
if result := isNumericExtension(tt.ext); result != tt.expected {
132+
t.Errorf("isNumericExtension(%s) = %v; want %v", tt.ext, result, tt.expected)
133+
}
134+
}
135+
}

agent/pkg/logs/size.go

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ func GetLogSizes(dirPath string) (LogSizes, error) {
5555
summary.TotalSize += fileSize
5656
summary.TotalFiles++
5757

58-
if extension == ".log" {
59-
summary.LogFilesSize += fileSize
60-
summary.LogFilesCount++
61-
} else if extension == ".gz" || extension == ".zip" || extension == ".tar" {
58+
if extension == ".log" || isRotatedLogFile(fileName) {
59+
summary.LogFilesSize += fileSize
60+
summary.LogFilesCount++
61+
} else if extension == ".gz" || extension == ".zip" || extension == ".tar" {
6262
summary.CompressedFilesSize += fileSize
6363
summary.CompressedFilesCount++
6464
}
@@ -100,20 +100,16 @@ func GetLogSize(filePath string) (LogSizes, error) {
100100
},
101101
},
102102
Summary: LogFilesSummary{
103-
TotalSize: fileSize,
104-
LogFilesSize: fileSize,
105-
CompressedFilesSize: 0,
106-
TotalFiles: 1,
107-
LogFilesCount: 1,
108-
CompressedFilesCount: 0,
103+
TotalSize: fileSize,
104+
TotalFiles: 1,
109105
},
110106
}
111107

112-
// If it's a compressed file, update the summary
113-
if extension == ".gz" || extension == ".zip" || extension == ".tar" {
114-
response.Summary.LogFilesSize = 0
108+
if extension == ".log" || isRotatedLogFile(fileName) {
109+
response.Summary.LogFilesSize = fileSize
110+
response.Summary.LogFilesCount = 1
111+
} else if extension == ".gz" || extension == ".zip" || extension == ".tar" {
115112
response.Summary.CompressedFilesSize = fileSize
116-
response.Summary.LogFilesCount = 0
117113
response.Summary.CompressedFilesCount = 1
118114
}
119115

0 commit comments

Comments
 (0)