Skip to content

AIR CLI Integration: Logs (air logs command) #5970

Open
riddhibhagwat-db wants to merge 5 commits into
air-clifrom
air-logs-m4
Open

AIR CLI Integration: Logs (air logs command) #5970
riddhibhagwat-db wants to merge 5 commits into
air-clifrom
air-logs-m4

Conversation

@riddhibhagwat-db

Copy link
Copy Markdown
Contributor

Changes

Implements the air logs JOB_RUN_ID command (previously a notImplemented stub) for the experimental AIR CLI. It fetches a run's training logs with a Bricklens-first, MLflow-fallback strategy:

  • Bricklens (primary): streams logs from the AiTraining log endpoint following an active run to completion, or tailing a completed one.
  • MLflow (fallback): when Bricklens is unavailable & gated off by a backend flag (FEATURE_DISABLED), not deployed (ENDPOINT_NOT_FOUND/404), or persistently failing. The command falls back to reading the run's MLflow log artifacts (chunk discovery + credential-vended download). The flag is evaluated server-side; the CLI only reads the response error code.

Flags:

  • --minutes restricts the fetch to the last N minutes (Bricklens time window).
  • --lines <N> is the tail the last N lines of a completed run. Mutually exclusive with --minutes.
  • --node, --retry is used to select a node / retry attempt. A past retry of a still-active run renders its (immutable) logs once instead of following the run.
  • --download-to / --review are rejected with a clear "not implemented" error (next PR follow up).

Why

air logs was the last unimplemented read command in the AIR CLI port. Bricklens is the primary log source, but it's behind a backend feature flag and isn't universally deployed, so the command must degrade gracefully to MLflow artifacts rather than fail. Bricklens is time-indexed (hence --minutes), while MLflow stores fixed log chunks (hence line-based --lines). the two flags map to what each backend can actually do.

Tests

  • Unit: classifyLogError fallback classification, --minutes/--lines window math, run-status projection, bounded dedup set, page draining + tail ordering, MLflow chunk listing/sorting and log-path discovery, flag validation, and an end-to-end Bricklens MLflow fallback through a mock server.
  • Acceptance: acceptance/experimental/air/logs/ (text + JSON streaming, --minutes, --lines, --retry, mutual-exclusion error, invalid ID, --download-to rejection) and logs-mlflow-fallback/ (Bricklens FEATURE_DISABLED, MLflow fallback, no-logs, text + JSON).

Implement the core of `air logs`: fetch a run's training logs from the
Bricklens-backed AiTrainingService endpoint, and on a backend-signaled failure
fall back to the MLflow log path. The feature flag is a backend SAFE flag — the
CLI never evaluates it; classifyLogError only reads the response error code
(FEATURE_DISABLED / ENDPOINT_NOT_FOUND / 404), and also falls back after
maxTransientFailures consecutive transient failures. The Bricklens/MLflow
decision is a single consolidated try/catch in fetchLogs.

Adds `--minutes N` (Bricklens-native time window) alongside `--lines N`
(MLflow-native line tail); the two are mutually exclusive and both flow through
the shared logRequest so either backend honors them. Streaming + status
resolution live in one file (logstream.go) as a small streamer type.

The MLflow fallback is a stub returning an explicit not-yet-implemented error
(phase 3 fills it in); --download-to and --review are rejected explicitly rather
than silently ignored.

Co-authored-by: Isaac
Add the `--retry` past-retry handling: a past retry of a still-active run has
immutable logs, so render them as a one-shot tail (drainStatic) instead of
following the run — otherwise the poll loop would wait forever for the run,
not the attempt, to finish. Mirrors handle_logs' viewing_past_retry. Exit code
still follows the run's result state (matching the Python `result_state ==
"SUCCESS"` rule), so a past retry of a running job prints its logs then exits
non-zero.

Also strengthen coverage of the phase-1 tail/streaming paths: a drainPages test
for boundary-second dedup and out-of-order skip, an acceptance case for --lines
tailing, and one for --retry.

Co-authored-by: Isaac
Replace the phase-1 stub with the real MLflow artifact path used when Bricklens
is gated off. It resolves the run's MLflow run id (via the existing mlflowIDs
helper), discovers the per-node log directory (logs/node_Y or the
attempt-prefixed logs/attempt_X/node_Y layout), lists the logs-N.chunk.txt chunk
files, and walks them newest-first — downloading each via the credential-vended
credentials-for-read pre-signed URL — until it has the requested tail, then
prints oldest-first. Mirrors the Python print_all_logs path.

--lines drives the tail as on the Bricklens path. MLflow chunk files are not
time-indexed, so --minutes can't restrict the window here; when only --minutes
is set the default tail cap applies and a debug line notes it was inapplicable.

Refactor the log-line and no-logs emitters into shared free functions
(emitLogLine / emitNoLogs) so the Bricklens and MLflow paths produce identical
raw and JSONL output instead of duplicating the logic.

Co-authored-by: Isaac
Make comments concise and self-contained: drop cross-references to the Python
CLI and remove restated-code noise. No behavior change.

Co-authored-by: Isaac
@github-actions

Copy link
Copy Markdown
Contributor

Approval status: pending

/acceptance/experimental/air/ - needs approval

12 files changed
Eligible: @apeforest, @bfontain, @lu-wang-dl, @panchalhp-db, @vinchenzo-db, @maggiewang-db, @ben-hansen-db, @pardis-beikzadeh-db

/experimental/air/ - needs approval

10 files changed
Eligible: @apeforest, @bfontain, @lu-wang-dl, @panchalhp-db, @vinchenzo-db, @maggiewang-db, @ben-hansen-db, @pardis-beikzadeh-db

Any maintainer (@andrewnester, @anton-107, @denik, @pietern, @shreyas-goenka, @simonfaltum, @renaudhartert-db, @janniklasrose, @lennartkats-db) can approve all areas.
See OWNERS for ownership rules.

Add direct unit tests for the JSONL emit path (emitLogLine / emitNoLogs), the
requestPage transient-retry loop (retries then falls back, and retries then
succeeds), and displayState. Make retryCheckInterval a var so the retry-loop
tests run without the 3s production wait.

Co-authored-by: Isaac
@eng-dev-ecosystem-bot

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: a594c75

Run: 29615565022

Env 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
💚​ aws linux 4 4 227 1123 6:13
💚​ aws windows 4 4 229 1121 6:59
💚​ aws-ucws linux 4 4 314 1040 5:19
💚​ aws-ucws windows 4 4 316 1038 7:26
💚​ azure linux 4 4 227 1122 6:07
💚​ azure windows 4 4 229 1120 7:16
💚​ azure-ucws linux 4 4 316 1037 6:33
💚​ azure-ucws windows 4 4 318 1035 8:03
💚​ gcp linux 4 4 226 1124 6:35
💚​ gcp windows 4 4 228 1122 6:42
8 interesting tests: 4 RECOVERED, 4 SKIP
Test Name aws linux aws windows aws-ucws linux aws-ucws windows azure linux azure windows azure-ucws linux azure-ucws windows gcp linux gcp windows
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/invariant/no_drift 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/ssh/connection 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestFetchRepositoryInfoAPI_FromRepo 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
💚​ TestFetchRepositoryInfoAPI_FromRepo/root 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
💚​ TestFetchRepositoryInfoAPI_FromRepo/subdir 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
Top 13 slowest tests (at least 2 minutes):
duration env testname
6:55 azure-ucws windows TestAccept
6:50 azure windows TestAccept
6:30 aws windows TestAccept
6:19 aws-ucws windows TestAccept
6:17 gcp windows TestAccept
3:37 gcp linux TestSecretsPutSecretStringValue
3:03 aws linux TestSecretsPutSecretStringValue
2:59 aws linux TestAccept
2:58 gcp linux TestAccept
2:55 azure linux TestAccept
2:51 aws-ucws linux TestAccept
2:48 azure-ucws linux TestAccept
2:21 azure linux TestSecretsPutSecretStringValue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants