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 --output-logs= option to turbo CLI #822

Merged
merged 14 commits into from
Mar 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
74 changes: 63 additions & 11 deletions cli/internal/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ type runSpec struct {
Opts *RunOptions
}

type LogsMode string

const (
FullLogs LogsMode = "full"
HashLogs LogsMode = "hash"
NoLogs LogsMode = "none"
)

func (rs *runSpec) ArgsForTask(task string) []string {
passThroughArgs := make([]string, 0, len(rs.Opts.passThroughArgs))
for _, target := range rs.Targets {
Expand Down Expand Up @@ -133,6 +141,11 @@ Options:
(default false)
--no-cache Avoid saving task results to the cache. Useful for
development/watch tasks. (default false)
--output-logs Set type of process output logging. Use full to show
all output. Use hash-only to show only turbo-computed
task hashes. Use new-only to show only new output with
only hashes for cached tasks. Use none to hide process
output. (default full)
--dry/--dry-run[=json] List the packages in scope and the tasks that would be run,
but don't actually run them. Passing --dry=json or
--dry-run=json will render the output in JSON format.
Expand Down Expand Up @@ -427,9 +440,15 @@ type RunOptions struct {
bail bool
passThroughArgs []string
// Restrict execution to only the listed task names. Default false
only bool
dryRun bool
dryRunJson bool
only bool
// Task logs output modes (cached and not cached tasks):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a good candidate for an enum

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Let me know if you prefer other names/values

// full - show all,
// hash - only show task hash,
// none - show nothing
cacheHitLogsMode LogsMode
cacheMissLogsMode LogsMode
dryRun bool
dryRunJson bool
}

func (ro *RunOptions) ScopeOpts() *scope.Opts {
Expand Down Expand Up @@ -457,6 +476,8 @@ func getDefaultRunOptions() *RunOptions {
forceExecution: false,
stream: true,
only: false,
cacheHitLogsMode: FullLogs,
cacheMissLogsMode: FullLogs,
}
}

Expand Down Expand Up @@ -559,6 +580,24 @@ func parseRunArgs(args []string, output cli.Ui) (*RunOptions, error) {
includDepsSet = true
case strings.HasPrefix(arg, "--only"):
runOptions.only = true
case strings.HasPrefix(arg, "--output-logs="):
outputLogsMode := arg[len("--output-logs="):]
switch outputLogsMode {
case "full":
runOptions.cacheMissLogsMode = FullLogs
runOptions.cacheHitLogsMode = FullLogs
case "none":
runOptions.cacheMissLogsMode = NoLogs
runOptions.cacheHitLogsMode = NoLogs
case "hash-only":
runOptions.cacheMissLogsMode = HashLogs
runOptions.cacheHitLogsMode = HashLogs
case "new-only":
runOptions.cacheMissLogsMode = FullLogs
runOptions.cacheHitLogsMode = HashLogs
default:
output.Warn(fmt.Sprintf("[WARNING] unknown value %v for --output-logs CLI flag. Falling back to full", outputLogsMode))
}
case strings.HasPrefix(arg, "--dry-run"):
runOptions.dryRun = true
if strings.HasPrefix(arg, "--dry-run=json") {
Expand Down Expand Up @@ -754,7 +793,7 @@ func (c *RunCommand) executeDryRun(engine *core.Scheduler, g *completeGraph, rs
}

// Replay logs will try to replay logs back to the stdout
func replayLogs(logger hclog.Logger, prefixUi cli.Ui, runOptions *RunOptions, logFileName, hash string, wg *sync.WaitGroup, silent bool) {
func replayLogs(logger hclog.Logger, prefixUi cli.Ui, runOptions *RunOptions, logFileName, hash string, wg *sync.WaitGroup, silent bool, outputLogsMode LogsMode) {
defer wg.Done()
logger.Debug("start replaying logs")
f, err := os.Open(filepath.Join(runOptions.cwd, logFileName))
Expand All @@ -763,9 +802,17 @@ func replayLogs(logger hclog.Logger, prefixUi cli.Ui, runOptions *RunOptions, lo
logger.Error(fmt.Sprintf("error reading logs: %v", err.Error()))
}
defer f.Close()
scan := bufio.NewScanner(f)
for scan.Scan() {
prefixUi.Output(ui.StripAnsi(string(scan.Bytes()))) //Writing to Stdout
if outputLogsMode != NoLogs {
scan := bufio.NewScanner(f)
if outputLogsMode == HashLogs {
//Writing to Stdout only the "cache hit, replaying output" line
scan.Scan()
prefixUi.Output(ui.StripAnsi(string(scan.Bytes())))
} else {
for scan.Scan() {
prefixUi.Output(ui.StripAnsi(string(scan.Bytes()))) //Writing to Stdout
}
}
}
logger.Debug("finish replaying logs")
}
Expand Down Expand Up @@ -864,18 +911,18 @@ func (e *execContext) exec(pt *packageTask) error {
} else if hit {
if e.rs.Opts.stream && fs.FileExists(filepath.Join(e.rs.Opts.cwd, logFileName)) {
e.logReplayWaitGroup.Add(1)
go replayLogs(targetLogger, e.ui, e.rs.Opts, logFileName, hash, &e.logReplayWaitGroup, false)
go replayLogs(targetLogger, e.ui, e.rs.Opts, logFileName, hash, &e.logReplayWaitGroup, false, e.rs.Opts.cacheHitLogsMode)
}
targetLogger.Debug("done", "status", "complete", "duration", time.Since(cmdTime))
tracer(TargetCached, nil)

return nil
}
if e.rs.Opts.stream {
if e.rs.Opts.stream && e.rs.Opts.cacheHitLogsMode != NoLogs {
targetUi.Output(fmt.Sprintf("cache miss, executing %s", ui.Dim(hash)))
}
} else {
if e.rs.Opts.stream {
if e.rs.Opts.stream && e.rs.Opts.cacheHitLogsMode != NoLogs {
targetUi.Output(fmt.Sprintf("cache bypass, force executing %s", ui.Dim(hash)))
}
}
Expand Down Expand Up @@ -921,7 +968,12 @@ func (e *execContext) exec(pt *packageTask) error {
bufWriter := bufio.NewWriter(output)
bufWriter.WriteString(fmt.Sprintf("%scache hit, replaying output %s\n", actualPrefix, ui.Dim(hash)))
defer bufWriter.Flush()
writer = io.MultiWriter(os.Stdout, bufWriter)
if e.rs.Opts.cacheMissLogsMode == NoLogs || e.rs.Opts.cacheMissLogsMode == HashLogs {
// only write to log file, not to stdout
writer = bufWriter
} else {
writer = io.MultiWriter(os.Stdout, bufWriter)
}
}

logger := log.New(writer, "", 0)
Expand Down
16 changes: 16 additions & 0 deletions cli/internal/run/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ func TestParseConfig(t *testing.T) {
profile: "",
cwd: defaultCwd,
cacheFolder: defaultCacheFolder,
cacheHitLogsMode: FullLogs,
cacheMissLogsMode: FullLogs,
},
},
{
Expand All @@ -56,6 +58,8 @@ func TestParseConfig(t *testing.T) {
profile: "",
cwd: "zop",
cacheFolder: filepath.FromSlash("zop/node_modules/.cache/turbo"),
cacheHitLogsMode: FullLogs,
cacheMissLogsMode: FullLogs,
},
},
{
Expand All @@ -74,6 +78,8 @@ func TestParseConfig(t *testing.T) {
scope: []string{"foo", "blah"},
cwd: defaultCwd,
cacheFolder: defaultCacheFolder,
cacheHitLogsMode: FullLogs,
cacheMissLogsMode: FullLogs,
},
},
{
Expand All @@ -91,6 +97,8 @@ func TestParseConfig(t *testing.T) {
profile: "",
cwd: defaultCwd,
cacheFolder: defaultCacheFolder,
cacheHitLogsMode: FullLogs,
cacheMissLogsMode: FullLogs,
},
},
{
Expand All @@ -108,6 +116,8 @@ func TestParseConfig(t *testing.T) {
profile: "",
cwd: defaultCwd,
cacheFolder: defaultCacheFolder,
cacheHitLogsMode: FullLogs,
cacheMissLogsMode: FullLogs,
},
},
{
Expand All @@ -126,6 +136,8 @@ func TestParseConfig(t *testing.T) {
cwd: defaultCwd,
cacheFolder: defaultCacheFolder,
passThroughArgs: []string{"--boop", "zoop"},
cacheHitLogsMode: FullLogs,
cacheMissLogsMode: FullLogs,
},
},
{
Expand All @@ -144,6 +156,8 @@ func TestParseConfig(t *testing.T) {
cwd: defaultCwd,
cacheFolder: defaultCacheFolder,
passThroughArgs: []string{},
cacheHitLogsMode: FullLogs,
cacheMissLogsMode: FullLogs,
},
},
{
Expand All @@ -160,6 +174,8 @@ func TestParseConfig(t *testing.T) {
cacheFolder: defaultCacheFolder,
scope: []string{"bar"},
since: "some-ref",
cacheHitLogsMode: FullLogs,
cacheMissLogsMode: FullLogs,
},
},
}
Expand Down
11 changes: 11 additions & 0 deletions docs/pages/docs/reference/command-line-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,17 @@ turbo run build --no-cache
turbo run dev --parallel --no-cache
```

#### `--output-logs`

`type: string`

Default `full`. Set type of process output logging. Use `full` to show all output. Use `hash-only` to show only turbo-computed task hashes. Use `new-only` to show only new output with only hashes for cached tasks. Use `none` to hide process output.

```shell
turbo run build --output-logs=full
turbo run build --output-logs=new-only
```

#### `--only`

Default false. Restricts execution to only include specified tasks. This is very similar to how how `lerna` or `pnpm` run tasks by default.
Expand Down