-
Notifications
You must be signed in to change notification settings - Fork 4k
Expand file tree
/
Copy pathbuffer.go
More file actions
151 lines (136 loc) · 4.08 KB
/
buffer.go
File metadata and controls
151 lines (136 loc) · 4.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package buffer
import (
"bytes"
"fmt"
"io"
"net/http"
"strings"
)
// maxLineSize is the maximum size for a single log line (10MB).
// GitHub Actions logs can contain extremely long lines (base64 content, minified JS, etc.)
const maxLineSize = 10 * 1024 * 1024
// ProcessResponseAsRingBufferToEnd reads the body of an HTTP response line by line,
// storing only the last maxJobLogLines lines using a ring buffer (sliding window).
// This efficiently retains the most recent lines, overwriting older ones as needed.
//
// Parameters:
//
// httpResp: The HTTP response whose body will be read.
// maxJobLogLines: The maximum number of log lines to retain.
//
// Returns:
//
// string: The concatenated log lines (up to maxJobLogLines), separated by newlines.
// int: The total number of lines read from the response.
// *http.Response: The original HTTP response.
// error: Any error encountered during reading.
//
// The function uses a ring buffer to efficiently store only the last maxJobLogLines lines.
// If the response contains more lines than maxJobLogLines, only the most recent lines are kept.
// Lines exceeding maxLineSize are truncated with a marker.
func ProcessResponseAsRingBufferToEnd(httpResp *http.Response, maxJobLogLines int) (string, int, *http.Response, error) {
if maxJobLogLines > 100000 {
maxJobLogLines = 100000
}
lines := make([]string, maxJobLogLines)
validLines := make([]bool, maxJobLogLines)
totalLines := 0
writeIndex := 0
const readBufferSize = 64 * 1024 // 64KB read buffer
const maxDisplayLength = 1000 // Keep first 1000 chars of truncated lines
readBuf := make([]byte, readBufferSize)
var currentLine strings.Builder
lineTruncated := false
for {
n, err := httpResp.Body.Read(readBuf)
if n > 0 {
chunk := readBuf[:n]
for len(chunk) > 0 {
// Find the next newline in the chunk
newlineIdx := bytes.IndexByte(chunk, '\n')
if newlineIdx >= 0 {
// Found a newline - complete the current line
if !lineTruncated {
remaining := maxLineSize - currentLine.Len()
if remaining > newlineIdx {
remaining = newlineIdx
}
if remaining > 0 {
currentLine.Write(chunk[:remaining])
}
if currentLine.Len() >= maxLineSize {
lineTruncated = true
}
}
// Store the completed line
line := currentLine.String()
if lineTruncated {
// Only keep first maxDisplayLength chars for truncated lines
if len(line) > maxDisplayLength {
line = line[:maxDisplayLength]
}
line += "... [TRUNCATED]"
}
lines[writeIndex] = line
validLines[writeIndex] = true
totalLines++
writeIndex = (writeIndex + 1) % maxJobLogLines
// Reset for next line
currentLine.Reset()
lineTruncated = false
chunk = chunk[newlineIdx+1:]
} else {
// No newline in remaining chunk - accumulate if not truncated
if !lineTruncated {
remaining := maxLineSize - currentLine.Len()
if remaining > len(chunk) {
remaining = len(chunk)
}
if remaining > 0 {
currentLine.Write(chunk[:remaining])
}
if currentLine.Len() >= maxLineSize {
lineTruncated = true
}
}
break
}
}
}
if err == io.EOF {
// Handle final line without newline
if currentLine.Len() > 0 {
line := currentLine.String()
if lineTruncated {
if len(line) > maxDisplayLength {
line = line[:maxDisplayLength]
}
line += "... [TRUNCATED]"
}
lines[writeIndex] = line
validLines[writeIndex] = true
totalLines++
}
break
}
if err != nil {
return "", 0, httpResp, fmt.Errorf("failed to read log content: %w", err)
}
}
var result []string
linesInBuffer := totalLines
if linesInBuffer > maxJobLogLines {
linesInBuffer = maxJobLogLines
}
startIndex := 0
if totalLines > maxJobLogLines {
startIndex = writeIndex
}
for i := 0; i < linesInBuffer; i++ {
idx := (startIndex + i) % maxJobLogLines
if validLines[idx] {
result = append(result, lines[idx])
}
}
return strings.Join(result, "\n"), totalLines, httpResp, nil
}