chore(core): add debug mode for tui (#31115)

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

Debugging the TUI is a chore

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

Debugging the TUI is a little better..

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Jason Jean 2025-05-09 11:57:32 -04:00 committed by GitHub
parent cdba055744
commit 0a2553aa2c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 169 additions and 18 deletions

52
Cargo.lock generated
View File

@ -335,6 +335,12 @@ version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
@ -753,6 +759,16 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"log",
"regex",
]
[[package]]
name = "equivalent"
version = "1.0.2"
@ -1022,6 +1038,15 @@ dependencies = [
"slab",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "gethostname"
version = "0.4.3"
@ -1723,9 +1748,9 @@ dependencies = [
[[package]]
name = "lazy_static"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lazycell"
@ -2209,6 +2234,7 @@ dependencies = [
"tracing",
"tracing-appender",
"tracing-subscriber",
"tui-logger",
"tui-term 0.2.0 (git+https://github.com/JamesHenry/tui-term?rev=88e3b61425c97220c528ef76c188df10032a75dd)",
"vt100-ctt",
"walkdir",
@ -3871,6 +3897,24 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tui-logger"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0073c168960eab3d93621cb5c7a49cabcff8977e95d160ec6cb465324d49bd7e"
dependencies = [
"chrono",
"env_filter",
"fxhash",
"lazy_static",
"log",
"parking_lot",
"ratatui",
"tracing",
"tracing-subscriber",
"unicode-segmentation",
]
[[package]]
name = "tui-term"
version = "0.2.0"
@ -3930,9 +3974,9 @@ dependencies = [
[[package]]
name = "unicode-segmentation"
version = "1.11.0"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-truncate"

View File

@ -54,6 +54,7 @@ tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
tokio = { version = "1.32.0", features = ['sync','macros','io-util','rt','time'] }
tokio-util = "0.7.9"
tracing-appender = "0.2"
tui-logger = { version = "0.17.2", features = ["tracing-support"] }
tui-term = { git = "https://github.com/JamesHenry/tui-term", rev = "88e3b61425c97220c528ef76c188df10032a75dd" }
walkdir = '2.3.3'
xxhash-rust = { version = '0.8.5', features = ['xxh3', 'xxh64'] }

View File

@ -237,6 +237,10 @@ export interface InputsInput {
export const IS_WASM: boolean
export declare export declare function logError(message: string): void
export declare export declare function logInfo(message: string): void
/** Stripped version of the NxJson interface for use in rust */
export interface NxJson {
namedInputs?: Record<string, Array<JsInputs>>

View File

@ -0,0 +1,14 @@
use crate::native::logger::enable_logger;
use tracing::{error, info};
#[napi]
pub fn log_info(message: String) {
enable_logger();
info!(message);
}
#[napi]
pub fn log_error(message: String) {
enable_logger();
error!(message);
}

View File

@ -1,3 +1,5 @@
pub mod console;
use colored::Colorize;
use std::env;
use std::fs::create_dir_all;
@ -8,6 +10,7 @@ use tracing_subscriber::fmt::{format, FmtContext, FormatEvent, FormatFields, For
use tracing_subscriber::prelude::*;
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::{EnvFilter, Layer};
use tui_logger::TuiTracingSubscriberLayer;
struct NxLogFormatter;
impl<S, N> FormatEvent<S, N> for NxLogFormatter
@ -103,7 +106,9 @@ pub(crate) fn enable_logger() {
.unwrap_or_else(|_| EnvFilter::new("ERROR")),
);
let registry = tracing_subscriber::registry().with(stdout_layer);
let registry = tracing_subscriber::registry()
.with(stdout_layer)
.with(TuiTracingSubscriberLayer);
if env::var("NX_NATIVE_FILE_LOGGING").is_err() {
// File logging is not enabled

View File

@ -1,7 +1,7 @@
pub mod cache;
pub mod glob;
pub mod hasher;
mod logger;
pub mod logger;
mod machine_id;
pub mod metadata;
pub mod plugins;

View File

@ -388,6 +388,8 @@ module.exports.getTransformableOutputs = nativeBinding.getTransformableOutputs
module.exports.hashArray = nativeBinding.hashArray
module.exports.hashFile = nativeBinding.hashFile
module.exports.IS_WASM = nativeBinding.IS_WASM
module.exports.logError = nativeBinding.logError
module.exports.logInfo = nativeBinding.logInfo
module.exports.parseTaskStatus = nativeBinding.parseTaskStatus
module.exports.remove = nativeBinding.remove
module.exports.restoreTerminal = nativeBinding.restoreTerminal

View File

@ -34,4 +34,5 @@ pub enum Action {
StartCommand(Option<u32>),
StartTasks(Vec<Task>),
EndTasks(Vec<TaskResult>),
ToggleDebugMode,
}

View File

@ -3,7 +3,7 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEventKind};
use hashbrown::HashSet;
use napi::bindgen_prelude::External;
use napi::threadsafe_function::{ErrorStrategy, ThreadsafeFunction};
use ratatui::layout::{Alignment, Rect, Size};
use ratatui::layout::{Alignment, Position, Rect, Size};
use ratatui::style::Modifier;
use ratatui::style::Style;
use ratatui::text::{Line, Span};
@ -14,6 +14,7 @@ use std::sync::{Arc, Mutex, RwLock};
use tokio::sync::mpsc;
use tokio::sync::mpsc::UnboundedSender;
use tracing::{debug, trace};
use tui_logger::{TuiLoggerSmartWidget, TuiLoggerWidget, TuiWidgetEvent, TuiWidgetState};
use vt100_ctt::Parser;
use crate::native::tui::tui::Tui;
@ -66,6 +67,8 @@ pub struct App {
selection_manager: Arc<Mutex<TaskSelectionManager>>,
pinned_tasks: Vec<String>,
tasks: Vec<Task>,
debug_mode: bool,
debug_state: TuiWidgetState,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -131,6 +134,8 @@ impl App {
pty_instances: HashMap::new(),
selection_manager,
tasks,
debug_mode: false,
debug_state: TuiWidgetState::default(),
})
}
@ -292,7 +297,7 @@ impl App {
tui::Event::Render => action_tx.send(Action::Render)?,
tui::Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?,
tui::Event::Key(key) => {
debug!("Handling Key Event: {:?}", key);
trace!("Handling Key Event: {:?}", key);
// Record that the user has interacted with the app
self.user_has_interacted = true;
@ -324,6 +329,16 @@ impl App {
};
}
if matches!(key.code, KeyCode::F(12)) {
self.dispatch_action(Action::ToggleDebugMode);
return Ok(false);
}
if self.debug_mode {
self.handle_debug_event(key);
return Ok(false);
}
// Only handle '?' key if we're not in interactive mode and the countdown popup is not open
if matches!(key.code, KeyCode::Char('?'))
&& !self.is_interactive_mode()
@ -729,7 +744,7 @@ impl App {
action_tx: &UnboundedSender<Action>,
) {
if action != Action::Tick && action != Action::Render {
debug!("{action:?}");
trace!("{action:?}");
}
match &action {
// Quit immediately
@ -749,6 +764,10 @@ impl App {
// Ensure the pty instances get resized appropriately (debounced)
let _ = self.debounce_pty_resize();
}
Action::ToggleDebugMode => {
self.debug_mode = !self.debug_mode;
debug!("Debug mode: {}", self.debug_mode);
}
Action::Render => {
tui.draw(|f| {
let area = f.area();
@ -764,6 +783,12 @@ impl App {
let frame_area = self.frame_area.unwrap();
let layout_areas = self.layout_areas.as_mut().unwrap();
if self.debug_mode {
let debug_widget = TuiLoggerSmartWidget::default().state(&self.debug_state);
f.render_widget(debug_widget, frame_area);
return;
}
// TODO: move this to the layout manager?
// Check for minimum viable viewport size at the app level
if frame_area.height < 10 || frame_area.width < 40 {
@ -1444,4 +1469,41 @@ impl App {
self.focus = focus;
self.dispatch_action(Action::UpdateFocus(focus));
}
fn handle_debug_event(&mut self, key: KeyEvent) {
// https://docs.rs/tui-logger/latest/tui_logger/#smart-widget-key-commands
// | KEY | ACTION
// |----------|-----------------------------------------------------------|
// | h | Toggles target selector widget hidden/visible
// | f | Toggle focus on the selected target only
// | UP | Select previous target in target selector widget
// | DOWN | Select next target in target selector widget
// | LEFT | Reduce SHOWN (!) log messages by one level
// | RIGHT | Increase SHOWN (!) log messages by one level
// | - | Reduce CAPTURED (!) log messages by one level
// | + | Increase CAPTURED (!) log messages by one level
// | PAGEUP | Enter Page Mode and scroll approx. half page up in log history.
// | PAGEDOWN | Only in page mode: scroll 10 events down in log history.
// | ESCAPE | Exit page mode and go back to scrolling mode
// | SPACE | Toggles hiding of targets, which have logfilter set to off
let debug_widget_event = match key.code {
KeyCode::Char(' ') => Some(TuiWidgetEvent::SpaceKey),
KeyCode::Esc => Some(TuiWidgetEvent::EscapeKey),
KeyCode::PageUp => Some(TuiWidgetEvent::PrevPageKey),
KeyCode::PageDown => Some(TuiWidgetEvent::NextPageKey),
KeyCode::Up => Some(TuiWidgetEvent::UpKey),
KeyCode::Down => Some(TuiWidgetEvent::DownKey),
KeyCode::Left => Some(TuiWidgetEvent::LeftKey),
KeyCode::Right => Some(TuiWidgetEvent::RightKey),
KeyCode::Char('+') => Some(TuiWidgetEvent::PlusKey),
KeyCode::Char('-') => Some(TuiWidgetEvent::MinusKey),
KeyCode::Char('h') => Some(TuiWidgetEvent::HideKey),
KeyCode::Char('f') => Some(TuiWidgetEvent::FocusKey),
_ => None,
};
if let Some(event) = debug_widget_event {
self.debug_state.transition(event);
}
}
}

View File

@ -162,6 +162,7 @@ impl AppLifeCycle {
done_callback: ThreadsafeFunction<(), ErrorStrategy::Fatal>,
) -> napi::Result<()> {
enable_logger();
tui_logger::init_logger(tui_logger::LevelFilter::Debug).unwrap();
debug!("Initializing Terminal UI");
let app_mutex = self.app.clone();

View File

@ -16,7 +16,7 @@ use tokio::{
task::JoinHandle,
};
use tokio_util::sync::CancellationToken;
use tracing::debug;
use tracing::{debug, trace};
pub type Frame<'a> = ratatui::Frame<'a>;
@ -100,13 +100,13 @@ impl Tui {
_event_tx.send(Event::Render).expect("cannot send event");
},
maybe_event = crossterm_event => {
debug!("Maybe Crossterm Event: {:?}", maybe_event);
trace!("Maybe Crossterm Event: {:?}", maybe_event);
match maybe_event {
Some(Ok(evt)) => {
debug!("Crossterm Event: {:?}", evt);
trace!("Crossterm Event: {:?}", evt);
match evt {
CrosstermEvent::Key(key) if key.kind == KeyEventKind::Press => {
debug!("Key: {:?}", key);
trace!("Key: {:?}", key);
_event_tx.send(Event::Key(key)).unwrap();
},
CrosstermEvent::Mouse(mouse) => {

View File

@ -17,7 +17,7 @@ import {
getTaskDetails,
hashTasksThatDoNotDependOnOutputsOfOtherTasks,
} from '../hasher/hash-task';
import { RunMode } from '../native';
import { logError, logInfo, RunMode } from '../native';
import {
runPostTasksExecution,
runPreTasksExecution,
@ -66,8 +66,8 @@ import {
} from './task-graph-utils';
import { TasksRunner, TaskStatus } from './tasks-runner';
import { shouldStreamOutput } from './utils';
import chalk = require('chalk');
import { signalToCode } from '../utils/exit-codes';
import chalk = require('chalk');
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
const originalStderrWrite = process.stderr.write.bind(process.stderr);
@ -207,10 +207,27 @@ async function getTerminalOutputLifeCycle(
* Patch stdout.write and stderr.write methods to pass Nx Cloud client logs to the TUI via the lifecycle
*/
const createPatchedLogWrite = (
originalWrite: typeof process.stdout.write | typeof process.stderr.write
originalWrite:
| typeof process.stdout.write
| typeof process.stderr.write,
isError: boolean
): typeof process.stdout.write | typeof process.stderr.write => {
// @ts-ignore
return (chunk, encoding, callback) => {
if (isError) {
logError(
Buffer.isBuffer(chunk)
? chunk.toString(encoding)
: chunk.toString()
);
} else {
logInfo(
Buffer.isBuffer(chunk)
? chunk.toString(encoding)
: chunk.toString()
);
}
// Check if the log came from the Nx Cloud client, otherwise invoke the original write method
const stackTrace = new Error().stack;
const isNxCloudLog = stackTrace.includes(
@ -251,8 +268,8 @@ async function getTerminalOutputLifeCycle(
};
};
process.stdout.write = createPatchedLogWrite(originalStdoutWrite);
process.stderr.write = createPatchedLogWrite(originalStderrWrite);
process.stdout.write = createPatchedLogWrite(originalStdoutWrite, false);
process.stderr.write = createPatchedLogWrite(originalStderrWrite, true);
// The cloud client calls console.log when NX_VERBOSE_LOGGING is set to true
console.log = createPatchedConsoleMethod(originalConsoleLog);