Skip to content

Commit 7abfb95

Browse files
authored
text-only output: log stdout/stderr of steps (#597)
This is part of sourcegraph/sourcegraph#23496 and adds the logging of STDOUT/STDERR of steps to the -text-only output. Please see the comment I left for more info on how the interface could be improved/extended. Also: turns out I don't need the process.Logger that was extracted in sourcegraph/sourcegraph#24545 and instead what's needed is the IntervalLogger here that flushes the captured output with a delay (so we don't create a new huge JSON message for every line)
1 parent dfa59fa commit 7abfb95

10 files changed

Lines changed: 289 additions & 13 deletions

File tree

go.mod

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ go 1.17
55
require (
66
github.com/Masterminds/semver v1.5.0
77
github.com/cockroachdb/errors v1.8.6
8+
github.com/cockroachdb/redact v1.1.3 // indirect
9+
github.com/derision-test/glock v0.0.0-20210316032053-f5b74334bb29
810
github.com/dustin/go-humanize v1.0.0
911
github.com/gobwas/glob v0.2.3
1012
github.com/google/go-cmp v0.5.6
@@ -19,7 +21,9 @@ require (
1921
github.com/sourcegraph/batch-change-utils v0.0.0-20210708162152-c9f35b905d94
2022
github.com/sourcegraph/go-diff v0.6.1
2123
github.com/sourcegraph/jsonx v0.0.0-20200629203448-1a936bd500cf
22-
github.com/sourcegraph/sourcegraph/lib v0.0.0-20210902215418-71593bf836f9
24+
github.com/sourcegraph/sourcegraph/lib v0.0.0-20210903080558-3f16affc1edc
25+
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
26+
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
2327
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
2428
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
2529
jaytaylor.com/html2text v0.0.0-20200412013138-3577fbdbcff7
@@ -28,7 +32,6 @@ require (
2832
require (
2933
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
3034
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f // indirect
31-
github.com/cockroachdb/redact v1.1.3 // indirect
3235
github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 // indirect
3336
github.com/ghodss/yaml v1.0.0 // indirect
3437
github.com/gogo/protobuf v1.3.2 // indirect
@@ -46,8 +49,6 @@ require (
4649
github.com/pkg/errors v0.9.1 // indirect
4750
github.com/rivo/uniseg v0.1.0 // indirect
4851
github.com/rogpeppe/go-internal v1.8.0 // indirect
49-
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
50-
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
5152
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
5253
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
5354
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect

go.sum

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpT
4343
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4444
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4545
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
46+
github.com/derision-test/glock v0.0.0-20210316032053-f5b74334bb29 h1:O07j7ewg0eFHRx+7bxCA/7z86HJexl7HLY9kyVMmPDo=
47+
github.com/derision-test/glock v0.0.0-20210316032053-f5b74334bb29/go.mod h1:jKtLdBMrF+XQatqvg46wiWdDfDSSDjdhO4dOM2FX9H4=
4648
github.com/derision-test/go-mockgen v1.1.2/go.mod h1:9H3VGTWYnL1VJoHHCuPKDpPFmNQ1uVyNlpX6P63l5Sk=
4749
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
4850
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
@@ -257,8 +259,8 @@ github.com/sourcegraph/go-diff v0.6.1 h1:hmA1LzxW0n1c3Q4YbrFgg4P99GSnebYa3x8gr0H
257259
github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
258260
github.com/sourcegraph/jsonx v0.0.0-20200629203448-1a936bd500cf h1:oAdWFqhStsWiiMP/vkkHiMXqFXzl1XfUNOdxKJbd6bI=
259261
github.com/sourcegraph/jsonx v0.0.0-20200629203448-1a936bd500cf/go.mod h1:ppFaPm6kpcHnZGqQTFhUIAQRIEhdQDWP1PCv4/ON354=
260-
github.com/sourcegraph/sourcegraph/lib v0.0.0-20210902215418-71593bf836f9 h1:afOQUKamJAosvaIgAJKFrJ7BbQ06BxJLlNU2Z+wfzOc=
261-
github.com/sourcegraph/sourcegraph/lib v0.0.0-20210902215418-71593bf836f9/go.mod h1:ZK8mHKWdapYI8iIQwsO7QUxf44RjiNGKkOZa9aYIjTc=
262+
github.com/sourcegraph/sourcegraph/lib v0.0.0-20210903080558-3f16affc1edc h1:iMV2Nc/fCn3PrG9vgFgXN2LdY6mDfcGuzQe9ObCT/U4=
263+
github.com/sourcegraph/sourcegraph/lib v0.0.0-20210903080558-3f16affc1edc/go.mod h1:ZK8mHKWdapYI8iIQwsO7QUxf44RjiNGKkOZa9aYIjTc=
262264
github.com/sourcegraph/yaml v1.0.1-0.20200714132230-56936252f152 h1:z/MpntplPaW6QW95pzcAR/72Z5TWDyDnSo0EOcyij9o=
263265
github.com/sourcegraph/yaml v1.0.1-0.20200714132230-56936252f152/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
264266
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
@@ -274,6 +276,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
274276
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
275277
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
276278
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
279+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
277280
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
278281
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
279282
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
@@ -283,8 +286,9 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
283286
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
284287
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
285288
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
286-
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
287289
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
290+
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
291+
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
288292
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
289293
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
290294
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=

internal/batches/executor/coordinator.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ package executor
22

33
import (
44
"context"
5+
"io"
56
"reflect"
67
"strconv"
78
"time"
89

910
"github.com/cockroachdb/errors"
1011
"github.com/hashicorp/go-multierror"
1112
batcheslib "github.com/sourcegraph/sourcegraph/lib/batches"
12-
1313
"github.com/sourcegraph/src-cli/internal/api"
1414
"github.com/sourcegraph/src-cli/internal/batches"
1515
"github.com/sourcegraph/src-cli/internal/batches/docker"
@@ -217,6 +217,9 @@ type TaskExecutionUI interface {
217217

218218
// TODO: This should be split up into methods that are more specific.
219219
TaskCurrentlyExecuting(*Task, string)
220+
221+
StepStdoutWriter(context.Context, *Task, int) io.WriteCloser
222+
StepStderrWriter(context.Context, *Task, int) io.WriteCloser
220223
}
221224

222225
// Execute executes the given Tasks and the importChangeset statements in the

internal/batches/executor/coordinator_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package executor
33
import (
44
"context"
55
"fmt"
6+
"io"
67
"strings"
78
"sync"
89
"testing"
@@ -543,7 +544,19 @@ func (d *dummyTaskExecutionUI) TaskChangesetSpecsBuilt(t *Task, specs []*batches
543544
d.specs[t] = specs
544545
}
545546

547+
type discardCloser struct {
548+
io.Writer
549+
}
550+
551+
func (discardCloser) Close() error { return nil }
552+
546553
func (d *dummyTaskExecutionUI) TaskCurrentlyExecuting(*Task, string) {}
554+
func (d *dummyTaskExecutionUI) StepStdoutWriter(ctx context.Context, task *Task, step int) io.WriteCloser {
555+
return discardCloser{io.Discard}
556+
}
557+
func (d *dummyTaskExecutionUI) StepStderrWriter(ctx context.Context, task *Task, step int) io.WriteCloser {
558+
return discardCloser{io.Discard}
559+
}
547560

548561
var _ taskExecutor = &dummyExecutor{}
549562

internal/batches/executor/executor.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ func (x *executor) do(ctx context.Context, task *Task, ui TaskExecutionUI) (err
177177
reportProgress: func(currentlyExecuting string) {
178178
ui.TaskCurrentlyExecuting(task, currentlyExecuting)
179179
},
180+
newUiStdoutWriter: ui.StepStdoutWriter,
181+
newUiStderrWriter: ui.StepStderrWriter,
180182
}
181183

182184
result, stepResults, err := runSteps(runCtx, opts)
@@ -191,7 +193,6 @@ func (x *executor) do(ctx context.Context, task *Task, ui TaskExecutionUI) (err
191193

192194
return nil
193195
}
194-
195196
func (x *executor) addResult(task *Task, result executionResult, stepResults []stepExecutionResult) {
196197
x.resultsMu.Lock()
197198
defer x.resultsMu.Unlock()

internal/batches/executor/run_steps.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"github.com/sourcegraph/src-cli/internal/batches/util"
2323
"github.com/sourcegraph/src-cli/internal/batches/workspace"
2424

25+
"github.com/sourcegraph/sourcegraph/lib/process"
26+
2527
yamlv3 "gopkg.in/yaml.v3"
2628
)
2729

@@ -65,6 +67,9 @@ type executionOpts struct {
6567

6668
logger log.TaskLogger
6769
reportProgress func(string)
70+
71+
newUiStdoutWriter func(context.Context, *Task, int) io.WriteCloser
72+
newUiStderrWriter func(context.Context, *Task, int) io.WriteCloser
6873
}
6974

7075
func runSteps(ctx context.Context, opts *executionOpts) (result executionResult, stepResults []stepExecutionResult, err error) {
@@ -310,14 +315,29 @@ func executeSingleStep(
310315

311316
var stdoutBuffer, stderrBuffer bytes.Buffer
312317

313-
cmd.Stdout = io.MultiWriter(&stdoutBuffer, opts.logger.PrefixWriter("stdout"))
314-
cmd.Stderr = io.MultiWriter(&stderrBuffer, opts.logger.PrefixWriter("stderr"))
318+
writerCtx, writerCancel := context.WithCancel(ctx)
319+
defer writerCancel()
320+
uiStdoutWriter := opts.newUiStdoutWriter(writerCtx, opts.task, i)
321+
uiStderrWriter := opts.newUiStderrWriter(writerCtx, opts.task, i)
322+
defer func() {
323+
uiStdoutWriter.Close()
324+
uiStderrWriter.Close()
325+
}()
326+
327+
stdout := io.MultiWriter(&stdoutBuffer, uiStdoutWriter, opts.logger.PrefixWriter("stdout"))
328+
stderr := io.MultiWriter(&stderrBuffer, uiStderrWriter, opts.logger.PrefixWriter("stderr"))
329+
330+
wg, err := process.PipeOutput(cmd, stdout, stderr)
331+
if err != nil {
332+
return bytes.Buffer{}, bytes.Buffer{}, errors.Wrap(err, "piping process output")
333+
}
315334

316335
opts.logger.Logf("[Step %d] run: %q, container: %q", i+1, step.Run, step.Container)
317336
opts.logger.Logf("[Step %d] full command: %q", i+1, strings.Join(cmd.Args, " "))
318337

319338
t0 := time.Now()
320339
err = cmd.Run()
340+
wg.Wait()
321341
elapsed := time.Since(t0).Round(time.Millisecond)
322342
if err != nil {
323343
opts.logger.Logf("[Step %d] took %s; error running Docker container: %+v", i+1, elapsed, err)
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package ui
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"time"
7+
8+
"github.com/derision-test/glock"
9+
)
10+
11+
// IntervalWriter is a io.Writer that flushes to the given sink on the given
12+
// interval.
13+
type IntervalWriter struct {
14+
sink func(string)
15+
16+
ticker glock.Ticker
17+
18+
// buf is used to keep partial lines buffered before flushing them (either
19+
// on the next newline or after tickDuration)
20+
buf *bytes.Buffer
21+
writes chan []byte
22+
writesDone chan struct{}
23+
24+
closed chan struct{}
25+
done chan struct{}
26+
}
27+
28+
func newIntervalWriter(ctx context.Context, ticker glock.Ticker, sink func(string)) *IntervalWriter {
29+
l := &IntervalWriter{
30+
sink: sink,
31+
ticker: ticker,
32+
33+
writes: make(chan []byte),
34+
writesDone: make(chan struct{}),
35+
36+
buf: &bytes.Buffer{},
37+
38+
closed: make(chan struct{}, 1),
39+
done: make(chan struct{}, 1),
40+
}
41+
42+
go l.writeLines(ctx)
43+
44+
return l
45+
}
46+
47+
// NewLogger returns a new Logger instance and spawns a goroutine in the
48+
// background that regularily flushed the logged output to the given sink.
49+
//
50+
// If the passed in ctx is canceled the goroutine will exit.
51+
func NewIntervalWriter(ctx context.Context, interval time.Duration, sink func(string)) *IntervalWriter {
52+
return newIntervalWriter(ctx, glock.NewRealTicker(interval), sink)
53+
}
54+
55+
func (l *IntervalWriter) flush() {
56+
if l.buf.Len() == 0 {
57+
return
58+
}
59+
l.sink(l.buf.String())
60+
l.buf.Reset()
61+
}
62+
63+
// Close flushes the
64+
func (l *IntervalWriter) Close() error {
65+
l.closed <- struct{}{}
66+
<-l.done
67+
return nil
68+
}
69+
70+
// Write handler of IntervalWriter.
71+
func (l *IntervalWriter) Write(p []byte) (int, error) {
72+
l.writes <- p
73+
<-l.writesDone
74+
return len(p), nil
75+
}
76+
77+
func (l *IntervalWriter) writeLines(ctx context.Context) {
78+
defer func() {
79+
l.flush()
80+
l.ticker.Stop()
81+
l.done <- struct{}{}
82+
}()
83+
84+
for {
85+
select {
86+
case <-ctx.Done():
87+
return
88+
89+
case <-l.closed:
90+
return
91+
92+
case w, ok := <-l.writes:
93+
if !ok {
94+
return
95+
}
96+
97+
if _, err := l.buf.Write(w); err != nil {
98+
break
99+
}
100+
l.writesDone <- struct{}{}
101+
case <-l.ticker.Chan():
102+
l.flush()
103+
}
104+
}
105+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package ui
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/derision-test/glock"
9+
)
10+
11+
func TestIntervalWriter(t *testing.T) {
12+
ctx, cancel := context.WithCancel(context.Background())
13+
14+
ch := make(chan string, 500)
15+
16+
sink := func(data string) {
17+
ch <- data
18+
}
19+
20+
ticker := glock.NewMockTicker(1 * time.Second)
21+
writer := newIntervalWriter(ctx, ticker, sink)
22+
23+
writer.Write([]byte("1"))
24+
select {
25+
case <-ch:
26+
t.Fatalf("ch has data")
27+
default:
28+
}
29+
30+
ticker.BlockingAdvance(1 * time.Second)
31+
32+
select {
33+
case d := <-ch:
34+
if d != "1" {
35+
t.Fatalf("wrong data in sink")
36+
}
37+
case <-time.After(1 * time.Second):
38+
t.Fatalf("ch has NO data")
39+
}
40+
41+
writer.Write([]byte("2"))
42+
writer.Write([]byte("3"))
43+
writer.Write([]byte("4"))
44+
writer.Write([]byte("5"))
45+
46+
select {
47+
case <-ch:
48+
t.Fatalf("ch has data")
49+
default:
50+
}
51+
52+
cancel()
53+
writer.Close()
54+
55+
select {
56+
case d := <-ch:
57+
if d != "2345" {
58+
t.Fatalf("wrong data in sink")
59+
}
60+
case <-time.After(1 * time.Second):
61+
t.Fatalf("ch has NO data")
62+
}
63+
}

0 commit comments

Comments
 (0)