Skip to content
Open
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
2 changes: 1 addition & 1 deletion acceptance/experimental/air/help/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ Usage:
Flags:
--download-to string Download all logs to this directory instead of printing
-h, --help help for logs
--lines int For completed runs, print the last N lines (default 10000)
--minutes int Fetch only logs from the last N minutes
--node int Fetch logs from this node
--retry int View logs from a specific retry attempt; -1 means latest (default -1)
--tail int For completed runs, print the last N log lines (default 10000)

Global Flags:
--debug enable debug logging
Expand Down
6 changes: 3 additions & 3 deletions acceptance/experimental/air/logs-download/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ Error: invalid --node 5: run has 2 node(s), indexed 0 to 1

Exit code: 1

=== download-to cannot be combined with --lines
>>> [CLI] experimental air logs 123 --download-to dl-logs --lines 50
Error: --download-to writes complete logs, so it cannot be combined with --lines or --minutes
=== download-to cannot be combined with --tail
>>> [CLI] experimental air logs 123 --download-to dl-logs --tail 50
Error: --download-to writes complete logs, so it cannot be combined with --tail or --minutes

Exit code: 1
4 changes: 2 additions & 2 deletions acceptance/experimental/air/logs-download/script
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ errcode trace $CLI experimental air logs 123 --download-to dl-logs
title "download-to with an out-of-range node is rejected"
errcode trace $CLI experimental air logs 123 --download-to dl-logs --node 5

title "download-to cannot be combined with --lines"
errcode trace $CLI experimental air logs 123 --download-to dl-logs --lines 50
title "download-to cannot be combined with --tail"
errcode trace $CLI experimental air logs 123 --download-to dl-logs --tail 50
20 changes: 10 additions & 10 deletions acceptance/experimental/air/logs/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ step 1
step 2
CUDA out of memory

=== logs with --lines
>>> [CLI] experimental air logs 123 --lines 1
=== logs with --tail
>>> [CLI] experimental air logs 123 --tail 1
CUDA out of memory

=== logs with --lines 0 prints nothing
>>> [CLI] experimental air logs 123 --lines 0
=== logs with --tail 0 prints nothing
>>> [CLI] experimental air logs 123 --tail 0
No logs available for run 123. Run terminated in state SUCCESS

=== logs from a specific retry
Expand All @@ -32,21 +32,21 @@ step 1
step 2
CUDA out of memory

=== logs --lines and --minutes are mutually exclusive
>>> [CLI] experimental air logs 123 --lines 100 --minutes 30
Error: cannot combine --lines with --minutes: --lines tails by line count, --minutes by time window
=== logs --tail and --minutes are mutually exclusive
>>> [CLI] experimental air logs 123 --tail 100 --minutes 30
Error: cannot combine --tail with --minutes: --tail selects by line count, --minutes by time window

Exit code: 1

=== logs --lines and --minutes are mutually exclusive (json)
>>> [CLI] experimental air logs 123 --lines 100 --minutes 30 -o json
=== logs --tail and --minutes are mutually exclusive (json)
>>> [CLI] experimental air logs 123 --tail 100 --minutes 30 -o json
{
"v": 1,
"ts": "[TIMESTAMP]",
"error": {
"code": "INVALID_ARGS",
"kind": "PERMANENT",
"message": "cannot combine --lines with --minutes: --lines tails by line count, --minutes by time window",
"message": "cannot combine --tail with --minutes: --tail selects by line count, --minutes by time window",
"retryable": false
}
}
Expand Down
16 changes: 8 additions & 8 deletions acceptance/experimental/air/logs/script
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ trace $CLI experimental air logs 123 -o json
title "logs with --minutes"
trace $CLI experimental air logs 123 --minutes 30

title "logs with --lines"
trace $CLI experimental air logs 123 --lines 1
title "logs with --tail"
trace $CLI experimental air logs 123 --tail 1

title "logs with --lines 0 prints nothing"
trace $CLI experimental air logs 123 --lines 0
title "logs with --tail 0 prints nothing"
trace $CLI experimental air logs 123 --tail 0

title "logs from a specific retry"
trace $CLI experimental air logs 123 --retry 0

title "logs --lines and --minutes are mutually exclusive"
errcode trace $CLI experimental air logs 123 --lines 100 --minutes 30
title "logs --tail and --minutes are mutually exclusive"
errcode trace $CLI experimental air logs 123 --tail 100 --minutes 30

title "logs --lines and --minutes are mutually exclusive (json)"
errcode trace $CLI experimental air logs 123 --lines 100 --minutes 30 -o json
title "logs --tail and --minutes are mutually exclusive (json)"
errcode trace $CLI experimental air logs 123 --tail 100 --minutes 30 -o json

title "invalid run id"
errcode trace $CLI experimental air logs notanumber
Expand Down
2 changes: 1 addition & 1 deletion experimental/air/cmd/logmlflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ var artifactDownloadClient = &http.Client{
// per-node log directory, lists the chunk files, and walks them newest-first
// until it has the requested tail, then prints oldest-first.
//
// The tail length is --lines, else the default cap. MLflow chunks are not
// The tail length is --tail, else the default cap. MLflow chunks are not
// time-indexed, so --minutes cannot restrict the window here.
func mlflowLogFallback(ctx context.Context, w *databricks.WorkspaceClient, out io.Writer, req logRequest, status logRunStatus) (bool, error) {
if req.windowMinutes > 0 {
Expand Down
24 changes: 12 additions & 12 deletions experimental/air/cmd/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
func newLogsCommand() *cobra.Command {
var (
node int
lines int
tail int
minutes int
retry int
downloadTo string
Expand All @@ -36,7 +36,7 @@ func newLogsCommand() *cobra.Command {
}

cmd.Flags().IntVar(&node, "node", 0, "Fetch logs from this node")
cmd.Flags().IntVar(&lines, "lines", 0, "For completed runs, print the last N lines (default 10000)")
cmd.Flags().IntVar(&tail, "tail", 0, "For completed runs, print the last N log lines (default 10000)")
cmd.Flags().IntVar(&minutes, "minutes", 0, "Fetch only logs from the last N minutes")
cmd.Flags().IntVar(&retry, "retry", -1, "View logs from a specific retry attempt; -1 means latest")
cmd.Flags().StringVar(&downloadTo, "download-to", "", "Download all logs to this directory instead of printing")
Expand Down Expand Up @@ -64,20 +64,20 @@ func newLogsCommand() *cobra.Command {

// A download always writes the full log, so a tail or time window would be
// silently dropped.
if downloadTo != "" && (cmd.Flags().Changed("lines") || minutes > 0) {
if downloadTo != "" && (cmd.Flags().Changed("tail") || minutes > 0) {
return renderError(ctx, cmd, "INVALID_ARGS", "PERMANENT", false,
errors.New("--download-to writes complete logs, so it cannot be combined with --lines or --minutes"))
errors.New("--download-to writes complete logs, so it cannot be combined with --tail or --minutes"))
}

// --lines (line tail) and --minutes (time window) answer the same question
// --tail (line tail) and --minutes (time window) answer the same question
// two ways, so reject both together rather than silently honoring one.
if lines > 0 && minutes > 0 {
if tail > 0 && minutes > 0 {
return renderError(ctx, cmd, "INVALID_ARGS", "PERMANENT", false,
errors.New("cannot combine --lines with --minutes: --lines tails by line count, --minutes by time window"))
errors.New("cannot combine --tail with --minutes: --tail selects by line count, --minutes by time window"))
}
if lines < 0 {
if tail < 0 {
return renderError(ctx, cmd, "INVALID_ARGS", "PERMANENT", false,
fmt.Errorf("invalid --lines %d: must be positive", lines))
fmt.Errorf("invalid --tail %d: must be positive", tail))
}
if minutes < 0 {
return renderError(ctx, cmd, "INVALID_ARGS", "PERMANENT", false,
Expand All @@ -98,11 +98,11 @@ func newLogsCommand() *cobra.Command {
fmt.Errorf("invalid JOB_RUN_ID %q: must be a positive integer", args[0]))
}

// -1 signals "unset" (use the default cap); an explicit --lines 0 stays 0
// -1 signals "unset" (use the default cap); an explicit --tail 0 stays 0
// and prints nothing.
tailLines := -1
if cmd.Flags().Changed("lines") {
tailLines = lines
if cmd.Flags().Changed("tail") {
tailLines = tail
}

// Only the streaming path prints resume guidance, so only it catches the
Expand Down
14 changes: 8 additions & 6 deletions experimental/air/cmd/logs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ func TestLogsCommandShape(t *testing.T) {
review := cmd.Flags().Lookup("review")
require.NotNil(t, review)
assert.True(t, review.Hidden)
assert.NotNil(t, cmd.Flags().Lookup("tail"))
assert.Nil(t, cmd.Flags().Lookup("lines"))
}

// runLogsCmd invokes the logs command's RunE with the given flags against a mock
Expand All @@ -55,16 +57,16 @@ func TestLogsFlagValidation(t *testing.T) {
wantMsg string
}{
{
name: "lines and minutes are mutually exclusive",
name: "tail and minutes are mutually exclusive",
args: []string{"5"},
flags: map[string]string{"lines": "100", "minutes": "10"},
wantMsg: "cannot combine --lines with --minutes",
flags: map[string]string{"tail": "100", "minutes": "10"},
wantMsg: "cannot combine --tail with --minutes",
},
{
name: "negative lines rejected",
name: "negative tail rejected",
args: []string{"5"},
flags: map[string]string{"lines": "-1"},
wantMsg: "invalid --lines",
flags: map[string]string{"tail": "-1"},
wantMsg: "invalid --tail",
},
{
name: "negative minutes rejected",
Expand Down
8 changes: 4 additions & 4 deletions experimental/air/cmd/logstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const (
// before falling back to MLflow.
maxTransientFailures = 5
// defaultCompletedRunTailLines caps a completed run's output when neither
// --lines nor --minutes is set.
// --tail nor --minutes is set.
defaultCompletedRunTailLines = 10000
// seenRecordsCap bounds the dedup set, evicting oldest-inserted entries first.
seenRecordsCap = 100000
Expand Down Expand Up @@ -89,7 +89,7 @@ type logRequest struct {
// windowMinutes, when > 0, restricts the fetch to the last N minutes.
windowMinutes int
// tailLines caps a completed run's output to the last N lines. Negative means
// --lines was unset (use the default cap); 0 prints nothing.
// --tail was unset (use the default cap); 0 prints nothing.
tailLines int
// downloadTo, when set, writes logs to that directory instead of stdout.
downloadTo string
Expand Down Expand Up @@ -462,8 +462,8 @@ func (st *bricklensStreamer) drainStatic(toSec int64) (bool, error) {
}

// tailTarget is the number of lines a tail keeps. A negative tailLines means
// --lines was unset, so use the default cap; 0 or more is taken literally (an
// explicit --lines 0 prints nothing).
// --tail was unset, so use the default cap; 0 or more is taken literally (an
// explicit --tail 0 prints nothing).
func (req logRequest) tailTarget() int {
if req.tailLines < 0 {
return defaultCompletedRunTailLines
Expand Down
Loading