mirror of https://github.com/ospab/ostp.git
refactor(logging): consolidate all logs into one ostp.log, Windows clears on start
Every process (CLI daemon, GUI, TUN helper) and every subsystem (tracing, the core event logger, the helper IPC, panic hook) wrote its own file: ostp-cli.log + ostp-core.log + ostp-helper.log + ostp-crash.log — a pile per run. Now they all funnel into a single ostp.log next to the exe. - logging: LOG_FILE_NAME/log_file_path() as the one source of truth; init_tracing gains a `truncate` arg. Truncation is gated twice: Windows-only (cfg!(windows)) AND daemon-only. One-shot commands (gk/check/init/-V/...) and the elevated TUN helper pass truncate=false so they can never wipe a running daemon's log; invocation_is_daemon() detects the daemon from argv. On Linux the server always appends (history kept, OS-rotated) as requested. - runner/helper manual writers + panic hook now target log_file_path(), so their output lands in the same ostp.log instead of separate files.
This commit is contained in:
parent
a33e5d3874
commit
5dc3a60017
|
|
@ -3,6 +3,53 @@ use std::io::Write;
|
|||
use std::path::PathBuf;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||
|
||||
/// The single canonical log file for the whole core. Every process (CLI daemon,
|
||||
/// GUI, TUN helper) and every subsystem (tracing, the core event logger, the
|
||||
/// helper IPC, panics) writes here — no more per-binary / per-subsystem sprawl
|
||||
/// (`ostp-cli.log` + `ostp-core.log` + `ostp-helper.log` + `ostp-crash.log`).
|
||||
pub const LOG_FILE_NAME: &str = "ostp.log";
|
||||
|
||||
/// Absolute path to the shared log file, next to the running executable.
|
||||
pub fn log_file_path() -> PathBuf {
|
||||
std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| p.parent().map(|d| d.join(LOG_FILE_NAME)))
|
||||
.unwrap_or_else(|| PathBuf::from(LOG_FILE_NAME))
|
||||
}
|
||||
|
||||
/// True if this invocation is the long-running daemon (a client/server run),
|
||||
/// as opposed to a one-shot subcommand (`gk`, `check`, `init`, `-V`, ...).
|
||||
///
|
||||
/// Used to gate log truncation: only the daemon clears the log at startup, so a
|
||||
/// one-shot command run while a daemon is live can never wipe the daemon's log.
|
||||
/// A daemon invocation is simply one that carries none of the one-shot tokens
|
||||
/// (`ostp`, `ostp run`, `ostp connect <url>` → daemon; everything else → one-shot).
|
||||
pub fn invocation_is_daemon<I: IntoIterator<Item = String>>(args: I) -> bool {
|
||||
const ONE_SHOT: &[&str] = &[
|
||||
"gk", "generate-key", "check", "init", "setup", "links", "import",
|
||||
"update", "migrate", "prober", "proxy-env", "proxy-env-clear",
|
||||
"uninstall", "-V", "--version", "-h", "--help", "help",
|
||||
];
|
||||
!args
|
||||
.into_iter()
|
||||
.skip(1) // program name
|
||||
.any(|a| ONE_SHOT.contains(&a.as_str()))
|
||||
}
|
||||
|
||||
/// Append a single timestamped line to the shared log file. Used by the manual
|
||||
/// writers (core event logger, TUN helper IPC) so their output lands in the same
|
||||
/// `ostp.log` as the tracing subscriber instead of a separate file.
|
||||
pub fn append_line(msg: &str) {
|
||||
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(log_file_path()) {
|
||||
let _ = writeln!(
|
||||
file,
|
||||
"[{}] {}",
|
||||
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
|
||||
msg
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_panic_hook() {
|
||||
std::panic::set_hook(Box::new(|info| {
|
||||
let payload = info.payload();
|
||||
|
|
@ -29,19 +76,16 @@ pub fn setup_panic_hook() {
|
|||
eprintln!("{}", crash_msg);
|
||||
tracing::error!("{}", crash_msg);
|
||||
|
||||
let path = std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| p.parent().map(|d| d.join("ostp-crash.log")))
|
||||
.unwrap_or_else(|| PathBuf::from("ostp-crash.log"));
|
||||
|
||||
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
|
||||
// Crashes land in the same shared log file (append — a crash must never
|
||||
// truncate, and the tracing worker may already be dead so we write direct).
|
||||
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(log_file_path()) {
|
||||
let _ = file.write_all(crash_msg.as_bytes());
|
||||
let _ = file.write_all(b"\n===================================================\n");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/// Initialises tracing and writes to `<app_name>.log` next to the executable.
|
||||
/// Initialises tracing and writes to the shared `ostp.log` next to the executable.
|
||||
///
|
||||
/// The `level` parameter controls the minimum log level:
|
||||
/// - `"error"` — only errors
|
||||
|
|
@ -51,7 +95,17 @@ pub fn setup_panic_hook() {
|
|||
/// - `"trace"` — all messages including very verbose internal state
|
||||
///
|
||||
/// The environment variable `RUST_LOG` overrides this value if set.
|
||||
pub fn init_tracing(level: &str, app_name: &str, version: &str) -> Option<tracing_appender::non_blocking::WorkerGuard> {
|
||||
///
|
||||
/// `truncate`: clear the log at startup. Honoured **only on Windows** — Linux
|
||||
/// servers keep their history (OS-rotated). Pass `true` only from the daemon's
|
||||
/// own entrypoint; one-shot commands and child processes (the TUN helper) pass
|
||||
/// `false` so they append instead of wiping a running daemon's log.
|
||||
pub fn init_tracing(
|
||||
level: &str,
|
||||
app_name: &str,
|
||||
version: &str,
|
||||
truncate: bool,
|
||||
) -> Option<tracing_appender::non_blocking::WorkerGuard> {
|
||||
// RUST_LOG overrides the config-derived level
|
||||
let env_filter = EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| {
|
||||
|
|
@ -66,12 +120,20 @@ pub fn init_tracing(level: &str, app_name: &str, version: &str) -> Option<tracin
|
|||
}
|
||||
});
|
||||
|
||||
let path = std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| p.parent().map(|d| d.join(format!("{}.log", app_name))))
|
||||
.unwrap_or_else(|| PathBuf::from(format!("{}.log", app_name)));
|
||||
let path = log_file_path();
|
||||
|
||||
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(&path) {
|
||||
let mut open_opts = OpenOptions::new();
|
||||
open_opts.create(true);
|
||||
// Truncate-on-startup is Windows-only and daemon-only. Everywhere else append:
|
||||
// Linux keeps server history, and one-shot commands / the TUN helper must not
|
||||
// wipe a running daemon's log.
|
||||
if truncate && cfg!(windows) {
|
||||
open_opts.write(true).truncate(true);
|
||||
} else {
|
||||
open_opts.append(true);
|
||||
}
|
||||
|
||||
if let Ok(mut file) = open_opts.open(&path) {
|
||||
// Write the startup banner directly to the log file, bypassing the
|
||||
// tracing subscriber entirely. Emitting it via tracing::info!() hits
|
||||
// BOTH layers below (file AND stderr), so every one-shot CLI command
|
||||
|
|
|
|||
|
|
@ -10,10 +10,9 @@ use std::fs::OpenOptions;
|
|||
use std::io::Write as _;
|
||||
|
||||
fn log_to_core_file(msg: &str) {
|
||||
let path = std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| p.parent().map(|d| d.join("ostp-core.log")))
|
||||
.unwrap_or_else(|| std::path::PathBuf::from("ostp-core.log"));
|
||||
// Writes into the single shared ostp.log (same file as the tracing appender),
|
||||
// not a separate ostp-core.log — see logging::LOG_FILE_NAME.
|
||||
let path = crate::logging::log_file_path();
|
||||
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
|
||||
let _ = writeln!(file, "[{}] {}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), msg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ fn main() {
|
|||
// Read config BEFORE init_tracing so we can use the correct log level from config.
|
||||
// If config is missing or debug=false we default to "info".
|
||||
let log_level = detect_log_level_from_config();
|
||||
let _log_guard = ostp_client::logging::init_tracing(&log_level, "ostp-gui", env!("CARGO_PKG_VERSION"));
|
||||
// The GUI launch IS the daemon's startup, so clear the shared log here
|
||||
// (Windows-only inside init_tracing). The elevated TUN helper spawned later
|
||||
// passes truncate=false so it appends instead of wiping this session's log.
|
||||
let _log_guard = ostp_client::logging::init_tracing(&log_level, "ostp-gui", env!("CARGO_PKG_VERSION"), true);
|
||||
|
||||
tracing::info!("ostp-gui starting (log_level={})", log_level);
|
||||
|
||||
|
|
@ -28,7 +31,7 @@ fn main() {
|
|||
{
|
||||
use std::ffi::OsStr;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
let msg_w: Vec<u16> = OsStr::new(&format!("OSTP GUI crashed:\n\n{}\n\nSee ostp-gui.log for details.", msg))
|
||||
let msg_w: Vec<u16> = OsStr::new(&format!("OSTP GUI crashed:\n\n{}\n\nSee ostp.log for details.", msg))
|
||||
.encode_wide().chain(Some(0)).collect();
|
||||
let title_w: Vec<u16> = OsStr::new("OSTP GUI — Fatal Error").encode_wide().chain(Some(0)).collect();
|
||||
#[link(name = "user32")] extern "system" {
|
||||
|
|
|
|||
|
|
@ -14,10 +14,8 @@ use portable_atomic::Ordering;
|
|||
fn log_to_file(msg: &str) {
|
||||
let msg = msg.to_string();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let path = std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| p.parent().map(|d| d.join("ostp-helper.log")))
|
||||
.unwrap_or_else(|| std::path::PathBuf::from("ostp-helper.log"));
|
||||
// Same shared ostp.log as everything else — not a separate ostp-helper.log.
|
||||
let path = ostp_client::logging::log_file_path();
|
||||
if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
|
||||
let _ = writeln!(file, "[{}] {}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), msg);
|
||||
}
|
||||
|
|
@ -53,7 +51,10 @@ struct TunnelState {
|
|||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
ostp_client::logging::setup_panic_hook();
|
||||
let _log_guard = ostp_client::logging::init_tracing("info", "ostp-helper", env!("CARGO_PKG_VERSION"));
|
||||
// The helper is a child of the GUI, which already truncated the shared log at
|
||||
// its own startup — pass false so the helper APPENDS instead of wiping the
|
||||
// GUI's session log.
|
||||
let _log_guard = ostp_client::logging::init_tracing("info", "ostp-helper", env!("CARGO_PKG_VERSION"), false);
|
||||
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(dir) = exe.parent() {
|
||||
|
|
|
|||
|
|
@ -251,7 +251,11 @@ async fn main() -> Result<()> {
|
|||
// where it does not apply.
|
||||
let _ = rlimit::increase_nofile_limit(1048576);
|
||||
ostp_client::logging::setup_panic_hook();
|
||||
let _log_guard = ostp_client::logging::init_tracing("info", "ostp-cli", env!("CARGO_PKG_VERSION"));
|
||||
// Clear the shared log at startup only when THIS invocation is the daemon —
|
||||
// a one-shot command (`ostp gk`, `ostp check`, ...) must not wipe a running
|
||||
// daemon's log. (Truncation itself is additionally Windows-only.)
|
||||
let is_daemon = ostp_client::logging::invocation_is_daemon(std::env::args());
|
||||
let _log_guard = ostp_client::logging::init_tracing("info", "ostp-cli", env!("CARGO_PKG_VERSION"), is_daemon);
|
||||
|
||||
let res = run_app().await;
|
||||
if let Err(e) = res {
|
||||
|
|
|
|||
Loading…
Reference in New Issue