Skip to content

Commit e654a7f

Browse files
authored
Merge pull request #7 from vdice/fix/monitor-job-logs-truncation
fix(monitor): use circular buffer to truncate job logs, if needed
2 parents bb23a3d + d5361d5 commit e654a7f

File tree

4 files changed

+66
-27
lines changed

4 files changed

+66
-27
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.15
55
replace k8s.io/client-go => k8s.io/client-go v0.18.2
66

77
require (
8+
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2
89
github.com/brigadecore/brigade/sdk/v2 v2.0.0-alpha.5.0.20210614205223-c89ad0ef0260
910
github.com/dgrijalva/jwt-go v3.2.0+incompatible
1011
github.com/google/go-github/v33 v33.0.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloDxZfhMm0xrLXZS8+COSu2bXmEQs=
2+
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
13
github.com/brigadecore/brigade/sdk/v2 v2.0.0-alpha.5.0.20210614205223-c89ad0ef0260 h1:BuuzEs84uw06myuo4G2j+fLiXMjwS/pXFDNp9i6e1Ds=
24
github.com/brigadecore/brigade/sdk/v2 v2.0.0-alpha.5.0.20210614205223-c89ad0ef0260/go.mod h1:rB3y/pIheORX5AHbxaSAw5Xr/U6bUAUtSLkgJcbOHIY=
35
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=

monitor/events.go

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import (
55
"fmt"
66
"log"
77
"strconv"
8-
"strings"
98
"time"
109

10+
"github.com/armon/circbuf"
11+
1112
"github.com/brigadecore/brigade/sdk/v2/core"
1213
"github.com/brigadecore/brigade/sdk/v2/meta"
1314
"github.com/google/go-github/v33/github"
@@ -401,7 +402,6 @@ func (m *monitor) getJobLogs(
401402
if !job.Status.Phase.IsTerminal() {
402403
return "", nil
403404
}
404-
var jobLogsBuilder strings.Builder
405405
logCh, errCh, err := m.logsClient.Stream(
406406
ctx,
407407
eventID,
@@ -413,23 +413,27 @@ func (m *monitor) getJobLogs(
413413
if err != nil {
414414
return "", err
415415
}
416-
// Arbitrarily limiting to 1000 log lines because we don't want to blow
417-
// out the heap if a Job has produced an enormous amount of logs
418-
const maxLines = 1000
419-
var i int
416+
// GitHub places a restriction on the text field for a Check Run at 65535
417+
// characters, so we use this as a maximum and truncate if needed.
418+
const maxBytes = 65535
419+
// This circular buffer can be written to infinitely but maintains a fixed
420+
// size, preserving the most recent bytes written - this is what we want
421+
// as we wish to present the last logs written.
422+
// Ignoring the error as it is only returned if the provided size is <= 0
423+
jobLogsBuffer, _ := circbuf.NewBuffer(maxBytes)
420424
logLoop:
421-
for i = 0; i < maxLines; i++ {
425+
for {
422426
var logEntry core.LogEntry
423427
var ok bool
424428
select {
425429
case logEntry, ok = <-logCh:
426430
if !ok { // The channel was closed. We got everything.
427431
break logLoop
428432
}
429-
if _, err = jobLogsBuilder.WriteString(logEntry.Message); err != nil {
433+
if _, err = jobLogsBuffer.Write([]byte(logEntry.Message)); err != nil {
430434
return "", err
431435
}
432-
if _, err = jobLogsBuilder.WriteString("\n"); err != nil {
436+
if _, err = jobLogsBuffer.Write([]byte("\n")); err != nil {
433437
return "", err
434438
}
435439
case err, ok = <-errCh:
@@ -440,15 +444,10 @@ logLoop:
440444
return "", ctx.Err()
441445
}
442446
}
443-
if i > maxLines {
444-
if _, err = jobLogsBuilder.WriteString(
445-
fmt.Sprintf(
446-
"--- !!! THESE LOGS HAVE BEEN TRUNCATED AFTER %d LINES !!! ---\n",
447-
maxLines,
448-
),
449-
); err != nil {
450-
return "", err
451-
}
447+
if jobLogsBuffer.TotalWritten() > maxBytes {
448+
logBytes := jobLogsBuffer.Bytes()
449+
copy(logBytes[0:], "(Previous text omitted)\n")
450+
return string(logBytes), nil
452451
}
453-
return jobLogsBuilder.String(), nil
452+
return jobLogsBuffer.String(), nil
454453
}

monitor/events_test.go

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"errors"
66
"fmt"
77
"strconv"
8-
"strings"
98
"testing"
109
"time"
1110

@@ -735,7 +734,7 @@ func TestGetJobLogs(t *testing.T) {
735734
},
736735
},
737736
{
738-
name: "success streaming logs",
737+
name: "success streaming logs, with truncation",
739738
monitor: &monitor{
740739
logsClient: &coreTesting.MockLogsClient{
741740
StreamFn: func(
@@ -747,14 +746,16 @@ func TestGetJobLogs(t *testing.T) {
747746
logEntryCh := make(chan core.LogEntry)
748747
errCh := make(chan error)
749748
go func() {
750-
// Send 2,000 lines
751-
for i := 0; i < 2000; i++ {
749+
// Send 32768 one-char lines for 65536 bytes total
750+
// (one-char msg + one-char newline)
751+
for i := 0; i < 32768; i++ {
752752
select {
753-
case logEntryCh <- core.LogEntry{Message: strconv.Itoa(i)}:
753+
case logEntryCh <- core.LogEntry{Message: "l"}:
754754
case <-ctx.Done():
755755
return
756756
}
757757
}
758+
close(logEntryCh)
758759
}()
759760
return logEntryCh, errCh, nil
760761
},
@@ -767,9 +768,45 @@ func TestGetJobLogs(t *testing.T) {
767768
},
768769
assertions: func(logs string, err error) {
769770
require.NoError(t, err)
770-
// Make sure we got back only the first 1,000 lines
771-
lines := strings.Split(logs, "\n")
772-
assert.Len(t, lines, 1001) // 1,001 accounts for the trailing newline
771+
assert.Contains(t, logs, "(Previous text omitted)\n")
772+
assert.Equal(t, len(logs), 65535)
773+
},
774+
},
775+
{
776+
name: "success streaming logs, no truncation",
777+
monitor: &monitor{
778+
logsClient: &coreTesting.MockLogsClient{
779+
StreamFn: func(
780+
ctx context.Context,
781+
_ string,
782+
_ *core.LogsSelector,
783+
_ *core.LogStreamOptions,
784+
) (<-chan core.LogEntry, <-chan error, error) {
785+
logEntryCh := make(chan core.LogEntry)
786+
errCh := make(chan error)
787+
go func() {
788+
for i := 0; i < 32767; i++ {
789+
select {
790+
case logEntryCh <- core.LogEntry{Message: "l"}:
791+
case <-ctx.Done():
792+
return
793+
}
794+
}
795+
close(logEntryCh)
796+
}()
797+
return logEntryCh, errCh, nil
798+
},
799+
},
800+
},
801+
job: core.Job{
802+
Status: &core.JobStatus{
803+
Phase: core.JobPhaseSucceeded,
804+
},
805+
},
806+
assertions: func(logs string, err error) {
807+
require.NoError(t, err)
808+
assert.NotContains(t, logs, "(Previous text omitted)\n")
809+
assert.Equal(t, len(logs), 65534)
773810
},
774811
},
775812
}

0 commit comments

Comments
 (0)