Igneum Miner: the app log reaches the intake (the uploader sends the file main.rs named instead of guessing a stamp); every upload starts with 'IGNEUM-APP version=<v> machine=<id8> platform=<os> node=<igneumd version>', logged again at every engine start so an OTA-applied restart carries it; token redaction kept

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-04 14:37:53 +00:00
parent 3f23df38b7
commit 0dd587d31a
4 changed files with 23 additions and 8 deletions

View file

@ -73,11 +73,13 @@ pub struct Shared {
cmd_tx: Mutex<Sender<Cmd>>, cmd_tx: Mutex<Sender<Cmd>>,
pub started: Instant, pub started: Instant,
engine_log: Mutex<Option<std::fs::File>>, engine_log: Mutex<Option<std::fs::File>>,
/// the app log's path: the one the uploader sends (main.rs names it; the engine must not guess the stamp)
pub log_path: PathBuf,
port: std::sync::atomic::AtomicU16, port: std::sync::atomic::AtomicU16,
} }
impl Shared { impl Shared {
pub fn new(token: String, runtime: Runtime, packaged: Packaged, settings: Settings, cmd_tx: Sender<Cmd>, engine_log: Option<std::fs::File>) -> Shared { pub fn new(token: String, runtime: Runtime, packaged: Packaged, settings: Settings, cmd_tx: Sender<Cmd>, engine_log: Option<std::fs::File>, log_path: PathBuf) -> Shared {
let settings_path = runtime.app_dir.join("settings.json"); let settings_path = runtime.app_dir.join("settings.json");
let wallet_path = runtime.app_dir.join("wallet.json"); let wallet_path = runtime.app_dir.join("wallet.json");
let mut st = State { version: VERSION.into(), ..Default::default() }; let mut st = State { version: VERSION.into(), ..Default::default() };
@ -113,10 +115,19 @@ impl Shared {
cmd_tx: Mutex::new(cmd_tx), cmd_tx: Mutex::new(cmd_tx),
started: Instant::now(), started: Instant::now(),
engine_log: Mutex::new(engine_log), engine_log: Mutex::new(engine_log),
log_path,
port: std::sync::atomic::AtomicU16::new(0), port: std::sync::atomic::AtomicU16::new(0),
} }
} }
/// The first line of every upload, so the console's Machines tab parses it without guessing:
/// IGNEUM-APP version=<v> machine=<id8> platform=<os> node=<igneumd version>. Logged at every engine start too,
/// so the restart after an OTA apply carries it again.
pub fn upload_header(&self) -> String {
let node = self.state.lock().unwrap().node.version.clone();
format!("IGNEUM-APP version={} machine={} platform={} node={}", VERSION, self.runtime.id8(), crate::manifest::platform_name(), if node.is_empty() { "unknown".to_string() } else { node.replace(' ', "_") })
}
pub fn set_port(&self, p: u16) { pub fn set_port(&self, p: u16) {
self.port.store(p, std::sync::atomic::Ordering::Relaxed); self.port.store(p, std::sync::atomic::Ordering::Relaxed);
} }
@ -504,6 +515,7 @@ impl Engine {
)); ));
let v = crate::detect::node_version(&self.bins.node); let v = crate::detect::node_version(&self.bins.node);
self.st().node.version = v.clone(); self.st().node.version = v.clone();
self.shared.log(&self.shared.upload_header());
// the prover service (proving v0): its own thread, idle until the setting is on // the prover service (proving v0): its own thread, idle until the setting is on
crate::prover::start(self.shared.clone(), self.bins.dir.clone()); crate::prover::start(self.shared.clone(), self.bins.dir.clone());
self.shared.log(&format!("node binary: {} ({v})", self.bins.node.display())); self.shared.log(&format!("node binary: {} ({v})", self.bins.node.display()));
@ -2106,7 +2118,7 @@ impl Engine {
} }
} }
let mut files: Vec<(String, PathBuf)> = Vec::new(); let mut files: Vec<(String, PathBuf)> = Vec::new();
files.push((self.label_base.clone(), self.shared.runtime.log_dir.join(format!("app-{}.log", self.stamp)))); files.push((self.label_base.clone(), self.shared.log_path.clone()));
if let Some(n) = &self.node_log { if let Some(n) = &self.node_log {
files.push((format!("nodelog-{}", self.label_base), n.clone())); files.push((format!("nodelog-{}", self.label_base), n.clone()));
} }
@ -2117,9 +2129,10 @@ impl Engine {
} }
// machine = "<hostname>-<id8>": readable in tools/logs.mjs and distinct for cloned PCs // machine = "<hostname>-<id8>": readable in tools/logs.mjs and distinct for cloned PCs
let (url, key, machine, run_id) = (p.log_intake_url.clone(), p.log_intake_key.clone(), format!("{}-{}", self.shared.runtime.host, self.shared.runtime.id8()), format!("{}-{}", self.label_base, self.stamp)); let (url, key, machine, run_id) = (p.log_intake_url.clone(), p.log_intake_key.clone(), format!("{}-{}", self.shared.runtime.host, self.shared.runtime.id8()), format!("{}-{}", self.label_base, self.stamp));
let header = self.shared.upload_header();
let t = std::thread::spawn(move || { let t = std::thread::spawn(move || {
for (label, path) in files { for (label, path) in files {
crate::update::upload_log(&url, &key, &label, &machine, &run_id, &path); crate::update::upload_log(&url, &key, &label, &machine, &run_id, &path, &header);
} }
}); });
if wait { if wait {

View file

@ -533,7 +533,7 @@ fn report(shared: &Arc<Shared>, job: &Job, sink: &Sink, status: &str, exit: i64,
return false; return false;
} }
let machine = format!("{}-{}", shared.runtime.host, shared.runtime.id8()); let machine = format!("{}-{}", shared.runtime.host, shared.runtime.id8());
crate::update::upload_log(&p.log_intake_url, &p.log_intake_key, &format!("job-{}", job.kind), &machine, &job.run_id(&shared.runtime.machine_id), &path) crate::update::upload_log(&p.log_intake_url, &p.log_intake_key, &format!("job-{}", job.kind), &machine, &job.run_id(&shared.runtime.machine_id), &path, &shared.upload_header())
} }
/// One file to the intake under the job's run id, labelled file-<name>. The intake keeps the last 256 KB. /// One file to the intake under the job's run id, labelled file-<name>. The intake keeps the last 256 KB.
@ -545,7 +545,7 @@ fn upload_file(shared: &Arc<Shared>, job: &Job, path: &Path, label_prefix: &str)
let name = path.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default(); let name = path.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default();
let label: String = format!("{label_prefix}-{name}").chars().take(80).collect(); let label: String = format!("{label_prefix}-{name}").chars().take(80).collect();
let machine = format!("{}-{}", shared.runtime.host, shared.runtime.id8()); let machine = format!("{}-{}", shared.runtime.host, shared.runtime.id8());
crate::update::upload_log(&p.log_intake_url, &p.log_intake_key, &label, &machine, &job.run_id(&shared.runtime.machine_id), path) crate::update::upload_log(&p.log_intake_url, &p.log_intake_key, &label, &machine, &job.run_id(&shared.runtime.machine_id), path, &shared.upload_header())
} }
// ---- fetching the jobs file and probing requirements ----------------------------------------------------------------- // ---- fetching the jobs file and probing requirements -----------------------------------------------------------------

View file

@ -87,7 +87,7 @@ fn main() {
let stamp_file = runtime.log_dir.join(format!("app-{}.log", stamp_now())); let stamp_file = runtime.log_dir.join(format!("app-{}.log", stamp_now()));
let engine_log = std::fs::File::create(&stamp_file).ok(); let engine_log = std::fs::File::create(&stamp_file).ok();
let (tx, rx) = channel(); let (tx, rx) = channel();
let shared = Arc::new(engine::Shared::new(token.clone(), runtime, packaged, settings, tx, engine_log)); let shared = Arc::new(engine::Shared::new(token.clone(), runtime, packaged, settings, tx, engine_log, stamp_file.clone()));
let port = match server::start(shared.clone()) { let port = match server::start(shared.clone()) {
Ok(p) => p, Ok(p) => p,
Err(e) => { Err(e) => {

View file

@ -64,7 +64,7 @@ mod tests {
} }
/// Posts the last 256 KB of a log file as {label, machine, run_id, lines}, as site/api/log.mjs expects. /// Posts the last 256 KB of a log file as {label, machine, run_id, lines}, as site/api/log.mjs expects.
pub fn upload_log(url: &str, key: &str, label: &str, machine: &str, run_id: &str, path: &std::path::Path) -> bool { pub fn upload_log(url: &str, key: &str, label: &str, machine: &str, run_id: &str, path: &std::path::Path, header: &str) -> bool {
if url.is_empty() || key.is_empty() { if url.is_empty() || key.is_empty() {
return false; return false;
} }
@ -76,7 +76,9 @@ pub fn upload_log(url: &str, key: &str, label: &str, machine: &str, run_id: &str
data = data.split_off(data.len() - 262_144); data = data.split_off(data.len() - 262_144);
} }
// second guard (R4.3.8): no line that carries a dashboard token leaves the machine // second guard (R4.3.8): no line that carries a dashboard token leaves the machine
let text: String = String::from_utf8_lossy(&data).lines().filter(|l| !crate::platform::carries_token(l)).map(|l| crate::platform::redact(l)).collect::<Vec<_>>().join("\n"); let tail: String = String::from_utf8_lossy(&data).lines().filter(|l| !crate::platform::carries_token(l)).map(|l| crate::platform::redact(l)).collect::<Vec<_>>().join("\n");
// the first line of every upload names the app, the machine and the node (the console parses it)
let text = format!("{header}\n{tail}");
let body = serde_json::json!({ "label": label, "machine": machine, "run_id": run_id, "lines": text }); let body = serde_json::json!({ "label": label, "machine": machine, "run_id": run_id, "lines": text });
let tmp = std::env::temp_dir().join(format!("igneum-upload-{}-{}.json", std::process::id(), label)); let tmp = std::env::temp_dir().join(format!("igneum-upload-{}-{}.json", std::process::id(), label));
if std::fs::write(&tmp, body.to_string()).is_err() { if std::fs::write(&tmp, body.to_string()).is_err() {