Skip to content

Commit

Permalink
write system logs to disk and use mc manifest to fetch java profiles
Browse files Browse the repository at this point in the history
  • Loading branch information
blarfoon committed Nov 5, 2024
1 parent 9935e0c commit 3effe8b
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ const LogsContent = (props: Props) => {
const [columns, setColumns] = createSignal<Columns>({
timestamp: true,
logger: true,
sourceKind: true,
sourceKind: false,
threadName: true,
level: true
});
Expand Down Expand Up @@ -254,7 +254,7 @@ const LogsContent = (props: Props) => {
class="justify-center hidden"
ref={(el) => props.assignScrollBottomRef(el)}
>
<div class="w-60 z-1 flex justify-center fixed bottom-6">
<div class="w-60 z-20 flex justify-center fixed bottom-6">
<ScrollBottomButton
onClick={props.scrollToBottom}
newLogsCount={props.newLogsCount}
Expand Down
23 changes: 10 additions & 13 deletions crates/carbon_app/src/managers/instance/log.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use carbon_parsing::log::{LogParser, ParsedItem};
use chrono::{DateTime, Local, NaiveDateTime, TimeZone};
use chrono::{DateTime, Local, NaiveDateTime, TimeZone, Utc};
use itertools::Itertools;
use serde::Serialize;
use std::{
Expand Down Expand Up @@ -294,26 +294,19 @@ impl ManagerRef<'_, InstanceManager> {
self.create_log(instance_id, Some(file_as_datetime)).await;

// read the file and send it to the log
let Ok(file) = tokio::fs::File::open(entry.path()).await else {
let Ok(mut file) = tokio::fs::File::open(entry.path()).await else {
tracing::error!({ file_name = ?file_name }, "Failed to open log file");
continue;
};
let mut reader = tokio::io::BufReader::new(file);

let mut buf = [0; 1024];

let mut stdout_processor =
LogProcessor::new(LogEntrySourceKind::StdOut, tx).await;

while let Ok(size) = reader.read(&mut buf).await {
if size == 0 {
break;
}
let mut buf = Vec::new();
let _ = file.read_to_end(&mut buf).await;

if let Err(e) = stdout_processor.process_data(&buf[..size], None).await
{
tracing::error!({ error = ?e }, "Failed to process stdout data");
}
if let Err(e) = stdout_processor.process_data(&buf, None).await {
tracing::error!({ error = ?e }, "Failed to process stdout data");
}
}
}
Expand All @@ -324,6 +317,10 @@ impl ManagerRef<'_, InstanceManager> {
}
}

pub fn format_message_as_log4j_event(message: &str) -> String {
format!("<log4j:Event logger=\"GDLAUNCHER\" timestamp=\"{}\" level=\"INFO\" thread=\"N/A\">\n\t<log4j:Message><![CDATA[{}]]></log4j:Message>\n</log4j:Event>\n", Utc::now().timestamp_millis(), message)
}

pub struct LogProcessor {
pub parser: LogParser,
pub kind: LogEntrySourceKind,
Expand Down
24 changes: 10 additions & 14 deletions crates/carbon_app/src/managers/instance/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1643,28 +1643,24 @@ impl<'s> ManagerRef<'s, InstanceManager> {
None => None,
};

let mut version_info = None;
let mut mc_manifest = None;

if let Some(mc_version) = &mc_version {
version_info = Some(
self.app
.minecraft_manager()
.get_minecraft_version(&mc_version)
.await,
);
let manifest = self.app.minecraft_manager().get_minecraft_manifest().await;
mc_manifest = manifest.ok();
}

let required_java_profile = mc_version.clone().and_then(|version| {
let version_info = version_info.unwrap().ok();
let java = version_info
.map(|version| version.java_version)
.and_then(|version| version.map(|version| version.component));

let Some(java) = java else {
let Some(manifest) = mc_manifest else {
return None;
};
let java = manifest
.versions
.iter()
.find(|profile| profile.id == version)
.and_then(|version| version.java_profile.clone());

let Ok(required_java) = MinecraftJavaProfile::try_from(java.as_str()) else {
let Some(required_java) = java else {
return None;
};

Expand Down
143 changes: 87 additions & 56 deletions crates/carbon_app/src/managers/instance/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ use crate::domain::modplatforms::curseforge::filters::ModFileParameters;
use crate::domain::modplatforms::modrinth::search::VersionID;
use crate::domain::runtime_path::InstancePath;
use crate::domain::vtask::VisualTaskId;
use crate::managers::instance::log::{GameLog, LogEntry, LogEntrySourceKind};
use crate::managers::instance::log::{
format_message_as_log4j_event, GameLog, LogEntry, LogEntrySourceKind,
};
use crate::managers::instance::modpack::packinfo;
use crate::managers::instance::schema::make_instance_config;
use crate::managers::java::java_checker::{JavaChecker, RealJavaChecker};
Expand Down Expand Up @@ -50,6 +52,7 @@ use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use tokio::sync::{watch, Mutex, Semaphore};
use tokio::task::JoinHandle;
use tokio::time::Instant;
use tokio::{io::AsyncReadExt, sync::mpsc};
use tracing::{debug, info, trace};

Expand Down Expand Up @@ -244,30 +247,66 @@ impl ManagerRef<'_, InstanceManager> {

let (log_id, log) = app.instance_manager().create_log(instance_id, None).await;

let now = Utc::now();

let log_file_name = format!("{}_{}", now.format("%Y-%m-%d"), now.format("%H-%M-%S"));

let logs_file_path = instance_path
.get_gdl_logs_path()
.join(format!("{}.log", log_file_name));

let logs_file_path_clone = logs_file_path.clone();

let file_fut = logs_file_path
.parent()
.map(|p| async {
if let Err(e) =
tokio::fs::create_dir_all(&logs_file_path_clone.parent().unwrap()).await
{
tracing::error!({ error = ?e }, "Failed to create log directory");
}
})
.map(|f| async {
f.await;
tokio::fs::File::create(&logs_file_path_clone).await
});

let mut file = match file_fut {
Some(f) => f.await.ok(),
None => None,
};

app.meta_cache_manager()
.watch_and_prioritize(Some(instance_id))
.await;

let result = app.instance_manager().list_mods(instance_id).await?;
let msg = format!(
"Mods ({} enabled / {} disabled): {}",
result.iter().filter(|mod_| mod_.enabled).count(),
result.iter().filter(|mod_| !mod_.enabled).count(),
result.into_iter().fold(String::new(), |mut acc, mod_| {
acc.push_str("\n\t [");
if mod_.enabled {
acc.push_str("x]");
} else {
acc.push_str(" ]");
}

log.send_modify(|log| {
log.add_entry(LogEntry::system_message(format!(
"Mods: {}",
result.into_iter().fold(String::new(), |mut acc, mod_| {
acc.push_str("\n\t [");
if mod_.enabled {
acc.push_str("x]");
} else {
acc.push_str(" ]");
}
acc.push(' ');
acc.push_str(&mod_.filename);

acc.push(' ');
acc.push_str(&mod_.filename);
acc
})
);

acc
})
)))
log.send_modify(|log| {
log.add_entry(LogEntry::system_message(msg.clone()));
});
if let Some(file) = file.as_mut() {
file.write_all(format_message_as_log4j_event(&msg).as_bytes())
.await?;
}

let installation_task = tokio::spawn(async move {
let instance_manager = app.instance_manager();
Expand Down Expand Up @@ -943,10 +982,16 @@ impl ManagerRef<'_, InstanceManager> {
|| anyhow::anyhow!("instance java version unsupported")
)?;

let msg = format!("Suggested Java Profile: {java_profile:?}");

log.send_modify(|log| {
log.add_entry(LogEntry::system_message(format!("Suggested Java Profile: {java_profile:?}")))
log.add_entry(LogEntry::system_message(msg.clone()))
});

if let Some(file) = file.as_mut() {
file.write_all(format_message_as_log4j_event(&msg).as_bytes()).await?;
}

let mut required_java_system_profile = SystemJavaProfileName::try_from(java_profile).with_context(
|| anyhow::anyhow!("System java version unsupported")
)?;
Expand Down Expand Up @@ -1132,10 +1177,16 @@ impl ManagerRef<'_, InstanceManager> {
}
};

let msg = format!("Using Java: {java:#?}");

log.send_modify(|log| {
log.add_entry(LogEntry::system_message(format!("Using Java: {java:#?}")))
log.add_entry(LogEntry::system_message(msg.clone()))
});

if let Some(file) = file.as_mut() {
file.write_all(format_message_as_log4j_event(&msg).as_bytes()).await?;
}

t_request_modloader_info.start_opaque();

for modloader in version.modloaders.iter() {
Expand Down Expand Up @@ -1242,7 +1293,7 @@ impl ManagerRef<'_, InstanceManager> {
t_request_minecraft_files.start_opaque();

let (lwjgl_group, version_files) = app.minecraft_manager()
.get_all_version_info_files(version_info.clone(), &java.arch, &log)
.get_all_version_info_files(version_info.clone(), &java.arch, &log, file.as_mut())
.await?;

downloads.extend(version_files);
Expand Down Expand Up @@ -1661,16 +1712,6 @@ impl ManagerRef<'_, InstanceManager> {

time_at_start = Some(Utc::now());

let log_file_name = format!(
"{}_{}",
start_time.format("%Y-%m-%d"),
start_time.format("%H-%M-%S")
);

let logs_file_path = instance_path
.get_gdl_logs_path()
.join(format!("{}.log", log_file_name));

tokio::select! {
_ = child.wait() => {
tracing::info!("Instance waited");
Expand All @@ -1679,7 +1720,7 @@ impl ManagerRef<'_, InstanceManager> {
tracing::info!("Instance killed");
drop(child.kill().await);
},
_ = read_logs(log.clone(), stdout, stderr, logs_file_path) => {
_ = read_logs(log.clone(), stdout, stderr, file.as_mut()) => {
tracing::info!("Instance read logs");
},
_ = update_playtime => {
Expand All @@ -1702,9 +1743,16 @@ impl ManagerRef<'_, InstanceManager> {
}

if let Ok(exitcode) = child.wait().await {
log.send_modify(|log| {
log.add_entry(LogEntry::system_message(format!("{exitcode}")))
});
let msg = format!("{exitcode}");

log.send_modify(|log| log.add_entry(LogEntry::system_message(msg.clone())));

if let Some(file) = file.as_mut() {
// TODO: not sure how to handle an error in here
let _ = file
.write_all(format_message_as_log4j_event(&msg).as_bytes())
.await;
}
}

let _ = app.rich_presence_manager().stop_activity().await;
Expand Down Expand Up @@ -1916,17 +1964,17 @@ async fn read_logs(
log: watch::Sender<GameLog>,
stdout: impl AsyncReadExt + Unpin + Send + 'static,
stderr: impl AsyncReadExt + Unpin + Send + 'static,
log_file_path: PathBuf,
file: Option<&mut File>,
) {
let (stdout_tx, stdout_rx) = mpsc::channel::<Vec<u8>>(1000);
let (stderr_tx, stderr_rx) = mpsc::channel::<Vec<u8>>(1000);

let stdout_task = tokio::spawn(read_pipe(stdout, stdout_tx));
let stderr_task = tokio::spawn(read_pipe(stderr, stderr_tx));

let process_task = tokio::spawn(process_logs(log, stdout_rx, stderr_rx, log_file_path));
process_logs(log, stdout_rx, stderr_rx, file).await;

let _ = tokio::join!(stdout_task, stderr_task, process_task);
let _ = tokio::join!(stdout_task, stderr_task);
}

async fn read_pipe(
Expand Down Expand Up @@ -1961,38 +2009,21 @@ async fn process_logs(
log: watch::Sender<GameLog>,
mut stdout_rx: mpsc::Receiver<Vec<u8>>,
mut stderr_rx: mpsc::Receiver<Vec<u8>>,
log_file_path: PathBuf,
mut file: Option<&mut File>,
) {
let file_fut = log_file_path
.parent()
.map(|p| async {
if let Err(e) = tokio::fs::create_dir_all(log_file_path.parent().unwrap()).await {
tracing::error!({ error = ?e }, "Failed to create log directory");
}
})
.map(|f| async {
f.await;
tokio::fs::File::create(&log_file_path).await
});

let mut file = match file_fut {
Some(f) => f.await.ok(),
None => None,
};

let mut stdout_processor = LogProcessor::new(LogEntrySourceKind::StdOut, log.clone()).await;

let mut stderr_processor = LogProcessor::new(LogEntrySourceKind::StdErr, log).await;

loop {
tokio::select! {
Some(data) = stdout_rx.recv() => {
if let Err(e) = stdout_processor.process_data(&data, file.as_mut()).await {
if let Err(e) = stdout_processor.process_data(&data, file.as_deref_mut()).await {
tracing::error!("Failed to process stdout data: {}", e);
}
}
Some(data) = stderr_rx.recv() => {
if let Err(e) = stderr_processor.process_data(&data, file.as_mut()).await {
if let Err(e) = stderr_processor.process_data(&data, file.as_deref_mut()).await {
tracing::error!("Failed to process stderr data: {}", e);
}
}
Expand Down
7 changes: 6 additions & 1 deletion crates/carbon_app/src/managers/minecraft/minecraft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use regex::{Captures, Regex};
use reqwest::Url;
use strum_macros::EnumIter;
use thiserror::Error;
use tokio::{process::Child, sync::Mutex};
use tokio::{process::Child, sync::Mutex, time::Instant};
use tracing::{info, trace, warn};

use crate::{
Expand Down Expand Up @@ -56,13 +56,18 @@ pub async fn get_manifest(
meta_base_url: &Url,
) -> anyhow::Result<VersionManifest> {
let server_url = meta_base_url.join(&format!("minecraft/{}/manifest.json", META_VERSION))?;
let time = Instant::now();
let new_manifest = reqwest_client
.get(server_url)
.send()
.await?
.json::<VersionManifest>()
.await?;

let elapsed = time.elapsed();

tracing::info!("Fetched manifest {}ms", elapsed.as_millis());

Ok(new_manifest)
}

Expand Down
Loading

0 comments on commit 3effe8b

Please sign in to comment.