Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --log-order option #3916

Merged
merged 44 commits into from
Jun 15, 2023
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
7ef10c4
Add log order flag to rust
rafaeltab Feb 21, 2023
55a49bb
Add basic functionality for grouping outputs
rafaeltab Feb 21, 2023
6920601
Implement usage of the flag
rafaeltab Feb 22, 2023
5ca0d14
Fix tests
rafaeltab Feb 22, 2023
a04aac4
Fix logger
rafaeltab Feb 22, 2023
5ddbd7b
Add new flag to existing tests
rafaeltab Feb 22, 2023
3246e1b
Add integration tests
rafaeltab Feb 22, 2023
6421960
Merge branch 'main' into rafaeltab/grouped-output
rafaeltab Feb 24, 2023
7cac2fd
Use a mutex to guarantee logorder inside a OutputLines call
rafaeltab Feb 25, 2023
0e3cbec
Merge branch 'main' into rafaeltab/grouped-output
rafaeltab Mar 7, 2023
e668dc5
Group turbo messages with the other output
rafaeltab Mar 7, 2023
b847e68
Replaced hashes in integration tests to make them pass
rafaeltab Mar 8, 2023
bcf332f
Add a comment
rafaeltab Mar 8, 2023
092aa35
Clarify name of variable
rafaeltab Mar 8, 2023
a444856
Implement a factory for cli.Uis
rafaeltab Mar 10, 2023
f761e72
WIP
rafaeltab Mar 10, 2023
90aca5f
Add queued ui
rafaeltab Mar 23, 2023
00ca62e
WIP
rafaeltab Mar 23, 2023
d4252b4
Functional implementation
rafaeltab Mar 23, 2023
ba1595a
Some improvements
rafaeltab Mar 24, 2023
34976a8
Use mutex to prevent rare log interleaving
rafaeltab Apr 2, 2023
a555359
Fix test
rafaeltab Apr 2, 2023
39c610e
Fix naming issues
rafaeltab Apr 2, 2023
d9b689d
Merge main into rafaeltab/grouped-output
rafaeltab Apr 2, 2023
2fa8a6b
Fix integration tests
rafaeltab Apr 2, 2023
cd6cad7
Output logs in goroutine
rafaeltab Apr 12, 2023
47924d6
Fix integration tests
rafaeltab Apr 12, 2023
06eb777
Use default_value_t
rafaeltab Apr 12, 2023
7a024f5
Use channel WIP
rafaeltab Apr 12, 2023
e9f2b17
Small improvements
rafaeltab Apr 15, 2023
c962c6e
Merge branch 'main' into rafaeltab/grouped-output
rafaeltab Apr 15, 2023
8b8d592
Merge branch 'main' into rafaeltab/grouped-output
rafaeltab Apr 18, 2023
dc89f32
Fix merge mistake
rafaeltab Apr 18, 2023
9d75182
Merge main into rafaeltab/grouped-output
rafaeltab May 28, 2023
8d2451b
Fix go lint problems
rafaeltab May 28, 2023
3c40e8f
Fix rust mismatched types
rafaeltab May 28, 2023
e0de43c
Fix integration tests
rafaeltab May 28, 2023
61535e9
Unnecessary null check
rafaeltab Jun 8, 2023
e34dcb6
Merge branch 'main' into rafaeltab/grouped-output
Jun 8, 2023
1f73aee
Apply comments
Jun 9, 2023
21a7922
Add test for default
Jun 9, 2023
9af7cae
Remove unnecessary empty output list
Jun 9, 2023
46cb415
whitespace
Jun 9, 2023
95ceaa7
Merge branch 'main' into rafaeltab/grouped-output
mehulkar Jun 15, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions cli/internal/cmdutil/cmdutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,22 @@ func (h *Helper) Cleanup(cliConfig *turbostate.ParsedArgsFromRust) {
}

func (h *Helper) getUI(cliArgs *turbostate.ParsedArgsFromRust) cli.Ui {
factory := h.getUIFactory(cliArgs)
return factory.Build(os.Stdout, os.Stdin, os.Stderr)
}

func (h *Helper) getUIFactory(cliArgs *turbostate.ParsedArgsFromRust) ui.Factory {
colorMode := ui.GetColorModeFromEnv()
if cliArgs.NoColor {
colorMode = ui.ColorModeSuppressed
}
if cliArgs.Color {
colorMode = ui.ColorModeForced
}
return ui.BuildColoredUi(colorMode)
return &ui.ColoredUIFactory{
ColorMode: colorMode,
Base: &ui.BasicUIFactory{},
}
}

func (h *Helper) getLogger() (hclog.Logger, error) {
Expand Down Expand Up @@ -126,7 +134,8 @@ func NewHelper(turboVersion string, args *turbostate.ParsedArgsFromRust) *Helper
// It additionally returns a mechanism to set an error, so
func (h *Helper) GetCmdBase(executionState *turbostate.ExecutionState) (*CmdBase, error) {
// terminal is for color/no-color output
terminal := h.getUI(&executionState.CLIArgs)
uiFactory := h.getUIFactory(&executionState.CLIArgs)
terminal := uiFactory.Build(os.Stdin, os.Stdout, os.Stderr)
// logger is configured with verbosity level using --verbosity flag from end users
logger, err := h.getLogger()
if err != nil {
Expand Down Expand Up @@ -155,6 +164,7 @@ func (h *Helper) GetCmdBase(executionState *turbostate.ExecutionState) (*CmdBase

return &CmdBase{
UI: terminal,
UIFactory: uiFactory,
Logger: logger,
RepoRoot: repoRoot,
APIClient: apiClient,
Expand All @@ -165,6 +175,7 @@ func (h *Helper) GetCmdBase(executionState *turbostate.ExecutionState) (*CmdBase
// CmdBase encompasses configured components common to all turbo commands.
type CmdBase struct {
UI cli.Ui
UIFactory ui.Factory
Logger hclog.Logger
RepoRoot turbopath.AbsoluteSystemPath
APIClient *client.APIClient
Expand Down
4 changes: 2 additions & 2 deletions cli/internal/logstreamer/logstreamer.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ type PrettyStdoutWriter struct {
var _ io.Writer = (*PrettyStdoutWriter)(nil)

// NewPrettyStdoutWriter returns an instance of PrettyStdoutWriter
func NewPrettyStdoutWriter(prefix string) *PrettyStdoutWriter {
func NewPrettyIoWriter(prefix string, ioWriter io.Writer) *PrettyStdoutWriter {
return &PrettyStdoutWriter{
w: os.Stdout,
w: ioWriter,
Prefix: prefix,
}
}
Expand Down
82 changes: 74 additions & 8 deletions cli/internal/run/real_run.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package run

import (
"bytes"
gocontext "context"
"fmt"
"io"
"log"
"os"
"os/exec"
"strings"
"sync"
Expand Down Expand Up @@ -75,11 +78,15 @@ func RealRun(

runCache := runcache.New(turboCache, base.RepoRoot, rs.Opts.runcacheOpts, colorCache)

concurrentUIFactory := ui.ConcurrentUIFactory{
Base: base.UIFactory,
}

ec := &execContext{
colorCache: colorCache,
runSummary: runSummary,
rs: rs,
ui: &cli.ConcurrentUi{Ui: base.UI},
ui: concurrentUIFactory.Build(os.Stdin, os.Stdout, os.Stderr),
runCache: runCache,
env: globalEnv,
passThroughEnv: globalPassThroughEnv,
Expand All @@ -97,10 +104,54 @@ func RealRun(
Concurrency: rs.Opts.runOpts.Concurrency,
}

mu := sync.Mutex{}
taskCount := len(engine.TaskGraph.Vertices())
rafaeltab marked this conversation as resolved.
Show resolved Hide resolved
logChan := make(chan taskLogContext, taskCount)
logWaitGroup := sync.WaitGroup{}
isGrouped := rs.Opts.runOpts.LogOrder == "grouped"

var outputLogs func()

if isGrouped {
outputLogs = func() {
for i := 1; i < taskCount; i++ {
logContext := <-logChan

outBytes := logContext.outBuf.Bytes()
errBytes := logContext.errBuf.Bytes()

_, errOut := os.Stdout.Write(outBytes)
_, errErr := os.Stderr.Write(errBytes)

if errOut != nil || errErr != nil {
ec.ui.Error("Failed to output some of the logs.")
}

logWaitGroup.Done()
}
}
}
if isGrouped {
go outputLogs()
}

taskSummaryMutex := sync.Mutex{}
taskSummaries := []*runsummary.TaskSummary{}
execFunc := func(ctx gocontext.Context, packageTask *nodes.PackageTask, taskSummary *runsummary.TaskSummary) error {
taskExecutionSummary, err := ec.exec(ctx, packageTask)
logWaitGroup.Add(1)
outBuf := &bytes.Buffer{}
errBuf := &bytes.Buffer{}

var outWriter io.Writer = os.Stdout
var errWriter io.Writer = os.Stderr

if isGrouped {
outWriter = outBuf
errWriter = errBuf
}

ui := concurrentUIFactory.Build(os.Stdin, outWriter, errWriter)
rafaeltab marked this conversation as resolved.
Show resolved Hide resolved

taskExecutionSummary, err := ec.exec(ctx, packageTask, ui, outWriter)

// taskExecutionSummary will be nil if the task never executed
// (i.e. if the workspace didn't implement the script corresponding to the task)
Expand All @@ -111,13 +162,19 @@ func RealRun(
taskSummary.CacheSummary = taskHashTracker.GetCacheStatus(taskSummary.TaskID)

// lock since multiple things to be appending to this array at the same time
mu.Lock()
taskSummaryMutex.Lock()
taskSummaries = append(taskSummaries, taskSummary)
// not using defer, just release the lock
mu.Unlock()
taskSummaryMutex.Unlock()

runSummary.CloseTask(taskSummary)
}
if isGrouped {
logChan <- taskLogContext{
outBuf: outBuf,
errBuf: errBuf,
}
}

// Return the error when there is one
if err != nil {
Expand Down Expand Up @@ -176,6 +233,10 @@ func RealRun(
}
}

if isGrouped {
logWaitGroup.Wait()
}

if err := runSummary.Close(ctx, exitCode, g.WorkspaceInfos); err != nil {
// We don't need to throw an error, but we can warn on this.
// Note: this method doesn't actually return an error for Real Runs at the time of writing.
Expand All @@ -190,6 +251,11 @@ func RealRun(
return nil
}

type taskLogContext struct {
outBuf *bytes.Buffer
errBuf *bytes.Buffer
}

type execContext struct {
colorCache *colorcache.ColorCache
runSummary runsummary.Meta
Expand All @@ -216,7 +282,7 @@ func (ec *execContext) logError(prefix string, err error) {
ec.ui.Error(fmt.Sprintf("%s%s%s", ui.ERROR_PREFIX, prefix, color.RedString(" %v", err)))
}

func (ec *execContext) exec(ctx gocontext.Context, packageTask *nodes.PackageTask) (*runsummary.TaskExecutionSummary, error) {
func (ec *execContext) exec(ctx gocontext.Context, packageTask *nodes.PackageTask, ui cli.Ui, outWriter io.Writer) (*runsummary.TaskExecutionSummary, error) {
// Setup tracer. Every time tracer() is called the taskExecutionSummary's duration is updated
// So make sure to call it before returning.
successExitCode := 0 // We won't use this till later
Expand Down Expand Up @@ -257,7 +323,7 @@ func (ec *execContext) exec(ctx gocontext.Context, packageTask *nodes.PackageTas
taskCache := ec.runCache.TaskCache(packageTask, hash)
// Create a logger for replaying
prefixedUI := &cli.PrefixedUi{
Ui: ec.ui,
Ui: ui,
OutputPrefix: prettyPrefix,
InfoPrefix: prettyPrefix,
ErrorPrefix: prettyPrefix,
Expand Down Expand Up @@ -328,7 +394,7 @@ func (ec *execContext) exec(ctx gocontext.Context, packageTask *nodes.PackageTas
// Setup stdout/stderr
// If we are not caching anything, then we don't need to write logs to disk
// be careful about this conditional given the default of cache = true
writer, err := taskCache.OutputWriter(prettyPrefix)
writer, err := taskCache.OutputWriter(prettyPrefix, outWriter)
if err != nil {
tracer(runsummary.TargetBuildFailed, err, nil)

Expand Down
2 changes: 2 additions & 0 deletions cli/internal/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,14 @@ func optsFromArgs(args *turbostate.ParsedArgsFromRust) (*Opts, error) {
}
opts.runOpts.Concurrency = concurrency
}

opts.runOpts.Parallel = runPayload.Parallel
opts.runOpts.Profile = runPayload.Profile
opts.runOpts.ContinueOnError = runPayload.ContinueExecution
opts.runOpts.Only = runPayload.Only
opts.runOpts.NoDaemon = runPayload.NoDaemon
opts.runOpts.SinglePackage = args.Command.Run.SinglePackage
opts.runOpts.LogOrder = args.Command.Run.LogOrder

// See comment on Graph in turbostate.go for an explanation on Graph's representation.
// If flag is passed...
Expand Down
8 changes: 4 additions & 4 deletions cli/internal/runcache/runcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,12 +224,12 @@ func (fwc *fileWriterCloser) Close() error {

// OutputWriter creates a sink suitable for handling the output of the command associated
// with this task.
func (tc TaskCache) OutputWriter(prefix string) (io.WriteCloser, error) {
func (tc TaskCache) OutputWriter(prefix string, ioWriter io.Writer) (io.WriteCloser, error) {
// an os.Stdout wrapper that will add prefixes before printing to stdout
stdoutWriter := logstreamer.NewPrettyStdoutWriter(prefix)
prettyIoWriter := logstreamer.NewPrettyIoWriter(prefix, ioWriter)

if tc.cachingDisabled || tc.rc.writesDisabled {
return nopWriteCloser{stdoutWriter}, nil
return nopWriteCloser{prettyIoWriter}, nil
}
// Setup log file
if err := tc.LogFileName.EnsureDir(); err != nil {
Expand All @@ -250,7 +250,7 @@ func (tc TaskCache) OutputWriter(prefix string) (io.WriteCloser, error) {
// only write to log file, not to stdout
fwc.Writer = bufWriter
} else {
fwc.Writer = io.MultiWriter(stdoutWriter, bufWriter)
fwc.Writer = io.MultiWriter(prettyIoWriter, bufWriter)
}

return fwc, nil
Expand Down
5 changes: 4 additions & 1 deletion cli/internal/scope/scope_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ func TestResolvePackages(t *testing.T) {
if err != nil {
t.Fatalf("cwd: %v", err)
}
tui := ui.Default()
defaultUIFactory := ui.ColoredUIFactory{
Base: &ui.BasicUIFactory{},
}
tui := defaultUIFactory.Build(os.Stdin, os.Stdout, os.Stderr)
logger := hclog.Default()
// Dependency graph:
//
Expand Down
1 change: 1 addition & 0 deletions cli/internal/turbostate/turbostate.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type RunPayload struct {
NoDeps bool `json:"no_deps"`
Only bool `json:"only"`
OutputLogs string `json:"output_logs"`
LogOrder string `json:"log_order"`
PassThroughArgs []string `json:"pass_through_args"`
Parallel bool `json:"parallel"`
Profile string `json:"profile"`
Expand Down
32 changes: 0 additions & 32 deletions cli/internal/ui/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (

"github.com/fatih/color"
"github.com/mattn/go-isatty"
"github.com/mitchellh/cli"
"github.com/vercel/turbo/cli/internal/ci"
)

Expand Down Expand Up @@ -88,34 +87,3 @@ func (into *stripAnsiWriter) Write(p []byte) (int, error) {
// written.
return len(p), nil
}

// Default returns the default colored ui
func Default() *cli.ColoredUi {
return BuildColoredUi(ColorModeUndefined)
}

func BuildColoredUi(colorMode ColorMode) *cli.ColoredUi {
colorMode = applyColorMode(colorMode)

var outWriter, errWriter io.Writer

if colorMode == ColorModeSuppressed {
outWriter = &stripAnsiWriter{wrappedWriter: os.Stdout}
errWriter = &stripAnsiWriter{wrappedWriter: os.Stderr}
} else {
outWriter = os.Stdout
errWriter = os.Stderr
}

return &cli.ColoredUi{
Ui: &cli.BasicUi{
Reader: os.Stdin,
Writer: outWriter,
ErrorWriter: errWriter,
},
OutputColor: cli.UiColorNone,
InfoColor: cli.UiColorNone,
WarnColor: cli.UiColor{Code: int(color.FgYellow), Bold: false},
ErrorColor: cli.UiColorRed,
}
}
Loading