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:
parent
cdba055744
commit
0a2553aa2c
52
Cargo.lock
generated
52
Cargo.lock
generated
@ -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"
|
||||
|
||||
@ -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'] }
|
||||
|
||||
4
packages/nx/src/native/index.d.ts
vendored
4
packages/nx/src/native/index.d.ts
vendored
@ -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>>
|
||||
|
||||
14
packages/nx/src/native/logger/console.rs
Normal file
14
packages/nx/src/native/logger/console.rs
Normal 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);
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -34,4 +34,5 @@ pub enum Action {
|
||||
StartCommand(Option<u32>),
|
||||
StartTasks(Vec<Task>),
|
||||
EndTasks(Vec<TaskResult>),
|
||||
ToggleDebugMode,
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user