boot.rs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. use crate::kernel::checkpoint::JamPaths;
  2. use crate::kernel::form::Kernel;
  3. use crate::{default_data_dir, NockApp};
  4. use chrono;
  5. use clap::{arg, command, ColorChoice, Parser};
  6. use nockvm::jets::hot::HotEntry;
  7. use std::fs;
  8. use std::path::PathBuf;
  9. use tracing::{debug, info, Level};
  10. use tracing_subscriber::fmt::format::Writer;
  11. use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields};
  12. use tracing_subscriber::layer::SubscriberExt;
  13. use tracing_subscriber::registry::LookupSpan;
  14. use tracing_subscriber::util::SubscriberInitExt;
  15. use tracing_subscriber::{fmt, EnvFilter};
  16. #[derive(Parser, Debug, Clone)]
  17. #[command(about = "boot a nockapp", author, version, color = ColorChoice::Auto)]
  18. pub struct Cli {
  19. #[arg(
  20. long,
  21. help = "Start with a new data directory, removing any existing data",
  22. default_value = "false"
  23. )]
  24. pub new: bool,
  25. #[arg(long, help = "Make an Sword trace", default_value = "false")]
  26. pub trace: bool,
  27. #[arg(
  28. long,
  29. default_value = "1000",
  30. help = "Set the save interval for checkpoints (in ms)"
  31. )]
  32. pub save_interval: u64,
  33. #[arg(long, help = "Control colored output", value_enum, default_value_t = ColorChoice::Auto)]
  34. pub color: ColorChoice,
  35. #[arg(
  36. long,
  37. help = "Path to a jam file containing existing kernel state. Supports both JammedCheckpoint and ExportedState formats."
  38. )]
  39. pub state_jam: Option<String>,
  40. #[arg(
  41. long,
  42. help = "Path to export the kernel state as a jam file in the ExportedState format."
  43. )]
  44. pub export_state_jam: Option<String>,
  45. }
  46. /// Result of setting up a NockApp
  47. pub enum SetupResult {
  48. /// A fully initialized NockApp
  49. App(NockApp),
  50. /// State was exported successfully
  51. ExportedState,
  52. }
  53. pub fn default_boot_cli(new: bool) -> Cli {
  54. Cli {
  55. save_interval: 1000,
  56. new,
  57. trace: false,
  58. color: ColorChoice::Auto,
  59. state_jam: None,
  60. export_state_jam: None,
  61. }
  62. }
  63. /// A minimal event formatter for development mode
  64. struct MinimalFormatter;
  65. impl<S, N> FormatEvent<S, N> for MinimalFormatter
  66. where
  67. S: tracing::Subscriber + for<'a> LookupSpan<'a>,
  68. N: for<'a> FormatFields<'a> + 'static,
  69. {
  70. fn format_event(
  71. &self,
  72. ctx: &FmtContext<'_, S, N>,
  73. mut writer: Writer<'_>,
  74. event: &tracing::Event<'_>,
  75. ) -> std::fmt::Result {
  76. let level = *event.metadata().level();
  77. let level_str = match level {
  78. Level::TRACE => "\x1B[36mT\x1B[0m",
  79. Level::DEBUG => "\x1B[34mD\x1B[0m",
  80. Level::INFO => "\x1B[32mI\x1B[0m",
  81. Level::WARN => "\x1B[33mW\x1B[0m",
  82. Level::ERROR => "\x1B[31mE\x1B[0m",
  83. };
  84. // Get level color code for potential use with slogger
  85. let level_color = match level {
  86. Level::TRACE => "\x1B[36m", // Cyan
  87. Level::DEBUG => "\x1B[34m", // Blue
  88. Level::INFO => "\x1B[32m", // Green
  89. Level::WARN => "\x1B[33m", // Yellow
  90. Level::ERROR => "\x1B[31m", // Red
  91. };
  92. write!(writer, "{} ", level_str)?;
  93. // simple, shorter timestamp (HH:mm:ss)
  94. let now = chrono::Local::now();
  95. let time_str = now.format("%H:%M:%S").to_string();
  96. write!(writer, "\x1B[38;5;246m({time_str})\x1B[0m ")?;
  97. let target = event.metadata().target();
  98. // Special handling for slogger
  99. if target == "slogger" {
  100. // For slogger, omit the target prefix and color the message with the log level color
  101. // this mimics the behavior of slogging in urbit
  102. write!(writer, "{}", level_color)?;
  103. ctx.field_format().format_fields(writer.by_ref(), event)?;
  104. write!(writer, "\x1B[0m")?;
  105. return writeln!(writer);
  106. }
  107. let simplified_target = if target.contains("::") {
  108. // Just take the last component of the module path
  109. let parts: Vec<&str> = target.split("::").collect();
  110. if parts.len() > 1 {
  111. // If we have a structure like "a::b::c::d", just take "c::d"
  112. // but prefix it with the first two characters of the first part
  113. // i.e, nockapp::kernel::boot -> [cr] kernel::boot
  114. if parts.len() > 2 {
  115. format!(
  116. "[{}] {}::{}",
  117. parts[0].chars().take(2).collect::<String>(),
  118. parts[parts.len() - 2],
  119. parts[parts.len() - 1]
  120. )
  121. } else {
  122. parts
  123. .last()
  124. .unwrap_or_else(|| {
  125. panic!(
  126. "Panicked at {}:{} (git sha: {:?})",
  127. file!(),
  128. line!(),
  129. option_env!("GIT_SHA")
  130. )
  131. })
  132. .to_string()
  133. }
  134. } else {
  135. target.to_string()
  136. }
  137. } else {
  138. target.to_string()
  139. };
  140. // Write the simplified target in grey and italics
  141. write!(writer, "\x1B[3;90m{}\x1B[0m: ", simplified_target)?;
  142. // Write the fields (the actual log message)
  143. ctx.field_format().format_fields(writer.by_ref(), event)?;
  144. writeln!(writer)
  145. }
  146. }
  147. /// Initialize tracing with appropriate configuration based on CLI arguments.
  148. ///
  149. /// This function sets up logging with different profiles:
  150. /// - Production mode: Full verbose logging as specified by log_level
  151. /// - Development mode: Cleaner, less noisy logging focused on application code
  152. ///
  153. /// In development mode, the base filter is set to INFO level, with application
  154. /// modules set to DEBUG. Additional modules can be specified with dev_modules.
  155. pub fn init_default_tracing(cli: &Cli) {
  156. let filter = EnvFilter::new(std::env::var("RUST_LOG").unwrap_or_else(|_| "trace".to_string()));
  157. let use_ansi = cli.color == ColorChoice::Auto || cli.color == ColorChoice::Always;
  158. // Build and initialize the subscriber based on format and mode
  159. match std::env::var("MINIMAL_LOG_FORMAT").unwrap_or_else(|_| "false".to_string()) == "true" {
  160. // Default pretty format for production
  161. false => {
  162. tracing_subscriber::registry()
  163. .with(
  164. fmt::layer()
  165. .with_ansi(use_ansi)
  166. .with_target(true)
  167. .with_level(true),
  168. )
  169. .with(filter)
  170. .init();
  171. }
  172. // Development mode with minimal formatter
  173. true => {
  174. let fmt_layer = fmt::layer()
  175. .with_ansi(use_ansi)
  176. .event_format(MinimalFormatter);
  177. tracing_subscriber::registry()
  178. .with(fmt_layer)
  179. .with(filter)
  180. .init();
  181. }
  182. }
  183. }
  184. pub async fn setup(
  185. jam: &[u8],
  186. cli: Option<Cli>,
  187. hot_state: &[HotEntry],
  188. name: &str,
  189. data_dir: Option<PathBuf>,
  190. ) -> Result<NockApp, Box<dyn std::error::Error>> {
  191. let result = setup_(
  192. jam,
  193. cli.unwrap_or_else(|| default_boot_cli(false)),
  194. hot_state,
  195. name,
  196. data_dir,
  197. )
  198. .await?;
  199. match result {
  200. SetupResult::App(app) => Ok(app),
  201. SetupResult::ExportedState => {
  202. info!("Exiting after successful state export");
  203. std::process::exit(0);
  204. }
  205. }
  206. }
  207. pub async fn setup_(
  208. jam: &[u8],
  209. cli: Cli,
  210. hot_state: &[HotEntry],
  211. name: &str,
  212. data_dir: Option<PathBuf>,
  213. ) -> Result<SetupResult, Box<dyn std::error::Error>> {
  214. let data_dir = if let Some(data_path) = data_dir.clone() {
  215. data_path.join(name)
  216. } else {
  217. default_data_dir(name)
  218. };
  219. let pma_dir = data_dir.join("pma");
  220. let jams_dir = data_dir.join("checkpoints");
  221. if !jams_dir.exists() {
  222. std::fs::create_dir_all(&jams_dir)?;
  223. debug!("Created jams directory: {:?}", jams_dir);
  224. }
  225. if pma_dir.exists() {
  226. std::fs::remove_dir_all(&pma_dir)?;
  227. debug!("Deleted existing pma directory: {:?}", pma_dir);
  228. }
  229. if cli.new && jams_dir.exists() {
  230. std::fs::remove_dir_all(&jams_dir)?;
  231. debug!("Deleted existing checkpoint directory: {:?}", jams_dir);
  232. }
  233. let jam_paths = JamPaths::new(&jams_dir);
  234. info!("kernel: starting");
  235. debug!("kernel: pma directory: {:?}", pma_dir);
  236. debug!(
  237. "kernel: jam buffer paths: {:?}, {:?}",
  238. jam_paths.0, jam_paths.1
  239. );
  240. let mut kernel = if let Some(state_path) = cli.state_jam {
  241. let state_bytes = fs::read(&state_path)?;
  242. debug!("kernel: loading state from jam file: {:?}", state_path);
  243. Kernel::load_with_kernel_state(pma_dir, jam_paths, jam, &state_bytes, hot_state, cli.trace)
  244. .await?
  245. } else {
  246. Kernel::load_with_hot_state(pma_dir, jam_paths, jam, hot_state, cli.trace).await?
  247. };
  248. if let Some(export_path) = cli.export_state_jam.clone() {
  249. export_kernel_state(&mut kernel, &export_path).await?;
  250. return Ok(SetupResult::ExportedState);
  251. }
  252. let save_interval = std::time::Duration::from_millis(cli.save_interval);
  253. let app = NockApp::new(kernel, save_interval).await;
  254. Ok(SetupResult::App(app))
  255. }
  256. /// Exports the kernel state to a jam file at the specified path
  257. async fn export_kernel_state(
  258. kernel: &mut Kernel,
  259. export_path: &str,
  260. ) -> Result<(), Box<dyn std::error::Error>> {
  261. info!("Extracting kernel state to file: {:?}", export_path);
  262. let state_bytes = kernel.create_state_bytes().await?;
  263. fs::write(export_path, state_bytes)?;
  264. info!("Successfully exported kernel state to: {:?}", export_path);
  265. Ok(())
  266. }