From 9702dae19dcba08d2b8f32d8028af747aefc4a13 Mon Sep 17 00:00:00 2001 From: Carter Date: Fri, 5 Jan 2024 11:00:08 -0800 Subject: [PATCH] Switch from stdout log to latest log MOD-595 (#964) * Switch from stdout log to latest log * remove std capture * Remove unused functions --- theseus/src/api/logs.rs | 8 -- theseus/src/launcher/mod.rs | 6 +- theseus/src/state/children.rs | 165 +----------------------- theseus_gui/src-tauri/src/api/logs.rs | 10 -- theseus_gui/src/helpers/logs.js | 4 - theseus_gui/src/pages/instance/Logs.vue | 30 +---- 6 files changed, 11 insertions(+), 212 deletions(-) diff --git a/theseus/src/api/logs.rs b/theseus/src/api/logs.rs index c3ab4cc92..6a1aa492c 100644 --- a/theseus/src/api/logs.rs +++ b/theseus/src/api/logs.rs @@ -241,14 +241,6 @@ pub async fn get_latest_log_cursor( get_generic_live_log_cursor(profile_path, "latest.log", cursor).await } -#[tracing::instrument] -pub async fn get_std_log_cursor( - profile_path: ProfilePathId, - cursor: u64, // 0 to start at beginning of file -) -> crate::Result { - get_generic_live_log_cursor(profile_path, "latest_stdout.log", cursor).await -} - #[tracing::instrument] pub async fn get_generic_live_log_cursor( profile_path: ProfilePathId, diff --git a/theseus/src/launcher/mod.rs b/theseus/src/launcher/mod.rs index 103caa845..1d670ba11 100644 --- a/theseus/src/launcher/mod.rs +++ b/theseus/src/launcher/mod.rs @@ -16,7 +16,7 @@ use daedalus as d; use daedalus::minecraft::{RuleAction, VersionInfo}; use st::Profile; use std::collections::HashMap; -use std::{process::Stdio, sync::Arc}; +use std::sync::Arc; use tokio::process::Command; use uuid::Uuid; @@ -511,9 +511,7 @@ pub async fn launch_minecraft( .into_iter() .collect::>(), ) - .current_dir(instance_path.clone()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); + .current_dir(instance_path.clone()); // CARGO-set DYLD_LIBRARY_PATH breaks Minecraft on macOS during testing on playground #[cfg(target_os = "macos")] diff --git a/theseus/src/state/children.rs b/theseus/src/state/children.rs index 0b474567f..76344987f 100644 --- a/theseus/src/state/children.rs +++ b/theseus/src/state/children.rs @@ -1,21 +1,12 @@ -use super::DirectoryInfo; use super::{Profile, ProfilePathId}; use chrono::{DateTime, Utc}; use serde::Deserialize; use serde::Serialize; -use std::path::Path; use std::{collections::HashMap, sync::Arc}; use sysinfo::PidExt; -use tokio::fs::File; -use tokio::io::AsyncBufReadExt; -use tokio::io::AsyncWriteExt; -use tokio::io::BufReader; use tokio::process::Child; -use tokio::process::ChildStderr; -use tokio::process::ChildStdout; use tokio::process::Command; use tokio::sync::RwLock; -use tracing::error; use crate::event::emit::emit_process; use crate::event::ProcessPayloadType; @@ -201,7 +192,6 @@ impl ChildType { pub struct MinecraftChild { pub uuid: Uuid, pub profile_relative_path: ProfilePathId, - pub output: Option, pub manager: Option>>, // None when future has completed and been handled pub current_child: Arc>, pub last_updated_playtime: DateTime, // The last time we updated the playtime for the associated profile @@ -281,44 +271,9 @@ impl Children { censor_strings: HashMap, ) -> crate::Result>> { // Takes the first element of the commands vector and spawns it - let mut child = mc_command.spawn().map_err(IOError::from)?; + let mc_proc = mc_command.spawn().map_err(IOError::from)?; - // Create std watcher threads for stdout and stderr - let log_path = DirectoryInfo::profile_logs_dir(&profile_relative_path) - .await? - .join("latest_stdout.log"); - let shared_output = - SharedOutput::build(&log_path, censor_strings).await?; - if let Some(child_stdout) = child.stdout.take() { - let stdout_clone = shared_output.clone(); - tokio::spawn(async move { - if let Err(e) = stdout_clone.read_stdout(child_stdout).await { - error!("Stdout process died with error: {}", e); - let _ = stdout_clone - .push_line(format!( - "Stdout process died with error: {}", - e - )) - .await; - } - }); - } - if let Some(child_stderr) = child.stderr.take() { - let stderr_clone = shared_output.clone(); - tokio::spawn(async move { - if let Err(e) = stderr_clone.read_stderr(child_stderr).await { - error!("Stderr process died with error: {}", e); - let _ = stderr_clone - .push_line(format!( - "Stderr process died with error: {}", - e - )) - .await; - } - }); - } - - let child = ChildType::TokioChild(child); + let child = ChildType::TokioChild(mc_proc); // Slots child into manager let pid = child.id().ok_or_else(|| { @@ -358,7 +313,6 @@ impl Children { let mchild = MinecraftChild { uuid, profile_relative_path, - output: Some(shared_output), current_child, manager, last_updated_playtime, @@ -449,7 +403,6 @@ impl Children { let mchild = MinecraftChild { uuid: cached_process.uuid, profile_relative_path: cached_process.profile_relative_path, - output: None, // No output for cached/rescued processes current_child, manager, last_updated_playtime, @@ -758,117 +711,3 @@ impl Default for Children { Self::new() } } - -// SharedOutput, a wrapper around a String that can be read from and written to concurrently -// Designed to be used with ChildStdout and ChildStderr in a tokio thread to have a simple String storage for the output of a child process -#[derive(Debug, Clone)] -pub struct SharedOutput { - log_file: Arc>, - censor_strings: HashMap, -} - -impl SharedOutput { - #[tracing::instrument(skip(censor_strings))] - async fn build( - log_file_path: &Path, - censor_strings: HashMap, - ) -> crate::Result { - // create log_file_path parent if it doesn't exist - let parent_folder = log_file_path.parent().ok_or_else(|| { - crate::ErrorKind::LauncherError(format!( - "Could not get parent folder of {:?}", - log_file_path - )) - })?; - tokio::fs::create_dir_all(parent_folder) - .await - .map_err(|e| IOError::with_path(e, parent_folder))?; - - Ok(SharedOutput { - log_file: Arc::new(RwLock::new( - File::create(log_file_path) - .await - .map_err(|e| IOError::with_path(e, log_file_path))?, - )), - censor_strings, - }) - } - - async fn read_stdout( - &self, - child_stdout: ChildStdout, - ) -> crate::Result<()> { - let mut buf_reader = BufReader::new(child_stdout); - let mut buf = Vec::new(); - - while buf_reader - .read_until(b'\n', &mut buf) - .await - .map_err(IOError::from)? - > 0 - { - let line = String::from_utf8_lossy(&buf).into_owned(); - let val_line = self.censor_log(line.clone()); - { - let mut log_file = self.log_file.write().await; - log_file - .write_all(val_line.as_bytes()) - .await - .map_err(IOError::from)?; - } - - buf.clear(); - } - Ok(()) - } - - async fn read_stderr( - &self, - child_stderr: ChildStderr, - ) -> crate::Result<()> { - let mut buf_reader = BufReader::new(child_stderr); - let mut buf = Vec::new(); - - // TODO: these can be asbtracted into noe function - while buf_reader - .read_until(b'\n', &mut buf) - .await - .map_err(IOError::from)? - > 0 - { - let line = String::from_utf8_lossy(&buf).into_owned(); - let val_line = self.censor_log(line.clone()); - { - let mut log_file = self.log_file.write().await; - log_file - .write_all(val_line.as_bytes()) - .await - .map_err(IOError::from)?; - } - - buf.clear(); - } - Ok(()) - } - - async fn push_line(&self, line: String) -> crate::Result<()> { - let val_line = self.censor_log(line.clone()); - { - let mut log_file = self.log_file.write().await; - log_file - .write_all(val_line.as_bytes()) - .await - .map_err(IOError::from)?; - } - - Ok(()) - } - - fn censor_log(&self, mut val: String) -> String { - for (find, replace) in &self.censor_strings { - val = val.replace(find, replace); - } - - val - } -} diff --git a/theseus_gui/src-tauri/src/api/logs.rs b/theseus_gui/src-tauri/src/api/logs.rs index cfea2efb3..d328aac4e 100644 --- a/theseus_gui/src-tauri/src/api/logs.rs +++ b/theseus_gui/src-tauri/src/api/logs.rs @@ -23,7 +23,6 @@ pub fn init() -> tauri::plugin::TauriPlugin { logs_delete_logs, logs_delete_logs_by_filename, logs_get_latest_log_cursor, - logs_get_std_log_cursor, ]) .build() } @@ -91,12 +90,3 @@ pub async fn logs_get_latest_log_cursor( ) -> Result { Ok(logs::get_latest_log_cursor(profile_path, cursor).await?) } - -/// Get live stdout log from a cursor -#[tauri::command] -pub async fn logs_get_std_log_cursor( - profile_path: ProfilePathId, - cursor: u64, // 0 to start at beginning of file -) -> Result { - Ok(logs::get_std_log_cursor(profile_path, cursor).await?) -} diff --git a/theseus_gui/src/helpers/logs.js b/theseus_gui/src/helpers/logs.js index f325a9f20..cd9fa70a0 100644 --- a/theseus_gui/src/helpers/logs.js +++ b/theseus_gui/src/helpers/logs.js @@ -54,7 +54,3 @@ export async function delete_logs(profilePath) { export async function get_latest_log_cursor(profilePath, cursor) { return await invoke('plugin:logs|logs_get_latest_log_cursor', { profilePath, cursor }) } -// For std log (from modrinth app written latest_stdout.log, contains stdout and stderr) -export async function get_std_log_cursor(profilePath, cursor) { - return await invoke('plugin:logs|logs_get_std_log_cursor', { profilePath, cursor }) -} diff --git a/theseus_gui/src/pages/instance/Logs.vue b/theseus_gui/src/pages/instance/Logs.vue index d39405fbc..3129e20cb 100644 --- a/theseus_gui/src/pages/instance/Logs.vue +++ b/theseus_gui/src/pages/instance/Logs.vue @@ -102,7 +102,7 @@ import { delete_logs_by_filename, get_logs, get_output_by_filename, - get_std_log_cursor, + get_latest_log_cursor, } from '@/helpers/logs.js' import { computed, nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue' import dayjs from 'dayjs' @@ -139,7 +139,7 @@ const props = defineProps({ const currentLiveLog = ref(null) const currentLiveLogCursor = ref(0) -const emptyText = ['No live game detected.', 'Start your game to proceed'] +const emptyText = ['No live game detected.', 'Start your game to proceed.'] const logs = ref([]) await setLogs() @@ -223,7 +223,7 @@ async function getLiveStdLog() { if (uuids.length === 0) { returnValue = emptyText.join('\n') } else { - const logCursor = await get_std_log_cursor( + const logCursor = await get_latest_log_cursor( props.instance.path, currentLiveLogCursor.value ).catch(handleError) @@ -243,31 +243,15 @@ async function getLogs() { return (await get_logs(props.instance.path, true).catch(handleError)) .reverse() .filter( + // filter out latest_stdout.log or anything without .log in it (log) => log.filename !== 'latest_stdout.log' && log.filename !== 'latest_stdout' && - log.stdout !== '' + log.stdout !== '' && + log.filename.includes('.log') ) .map((log) => { - if (log.filename == 'latest.log') { - log.name = 'Latest Log' - } else { - let filename = log.filename.split('.')[0] - let day = dayjs(filename.slice(0, 10)) - if (day.isValid()) { - if (day.isToday()) { - log.name = 'Today' - } else if (day.isYesterday()) { - log.name = 'Yesterday' - } else { - log.name = day.format('MMMM D, YYYY') - } - // Displays as "Today-1", "Today-2", etc, matching minecraft log naming but with the date - log.name = log.name + filename.slice(10) - } else { - log.name = filename - } - } + log.name = log.filename || 'Unknown' log.stdout = 'Loading...' return log })