use std::process;
use std::time::Instant;
use clap::{CommandFactory, Parser};
use omni_dev::request_log::{self, InvocationOutcome, RequestLogContext, Source};
use omni_dev::Cli;
fn main() {
let argv: Vec<String> = std::env::args().collect();
let command = resolve_command_path(&argv);
let daemon_run = is_daemon_run(&command);
init_tracing(daemon_run);
let cli = Cli::parse();
let source = if daemon_run {
Source::Daemon
} else {
Source::Cli
};
request_log::set_global(RequestLogContext {
invocation_id: request_log::new_id(),
source,
mcp_tool: None,
});
#[cfg(all(target_os = "macos", feature = "menu-bar"))]
if let Some(run_config) = cli.menu_bar_run_config() {
let cfg = match run_config {
Ok(cfg) => cfg,
Err(e) => die(&e),
};
match omni_dev::daemon::tray::run(cfg) {
Ok(()) => return,
Err(e) => die(&e),
}
}
let runtime = match tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
{
Ok(runtime) => runtime,
Err(e) => {
eprintln!("Error: failed to start the tokio runtime: {e}");
process::exit(1);
}
};
let start = Instant::now();
let result = runtime.block_on(cli.execute());
let (exit_code, error) = match &result {
Ok(()) => (0, None),
Err(e) => (1, Some(format!("{e:#}"))),
};
request_log::record_invocation(InvocationOutcome {
command,
command_line: argv,
exit_code,
error,
duration: start.elapsed(),
});
if let Err(e) = result {
die(&e);
}
}
fn resolve_command_path(argv: &[String]) -> Vec<String> {
let mut path = Vec::new();
let Ok(matches) = Cli::command().try_get_matches_from(argv) else {
return path;
};
let mut current = &matches;
while let Some((name, sub)) = current.subcommand() {
path.push(name.to_string());
current = sub;
}
path
}
fn is_daemon_run(command: &[String]) -> bool {
command.first().map(String::as_str) == Some("daemon")
&& command.get(1).map(String::as_str) == Some("run")
}
fn default_filter(daemon_run: bool) -> &'static str {
if daemon_run {
"info"
} else {
"warn"
}
}
fn init_tracing(daemon_run: bool) {
let (daemon_filter, settings_error) = if daemon_run {
daemon_filter_from(omni_dev::utils::settings::Settings::load())
} else {
(None, None)
};
let env_filter = std::env::var("RUST_LOG").ok();
let (filter, rejected) =
resolve_filter(daemon_run, env_filter.as_deref(), daemon_filter.as_deref());
tracing_subscriber::fmt()
.with_writer(std::io::stderr)
.with_env_filter(filter)
.init();
emit_bootstrap_warnings(&rejected, settings_error.as_deref());
}
fn daemon_filter_from(
loaded: anyhow::Result<omni_dev::utils::settings::Settings>,
) -> (Option<String>, Option<String>) {
match loaded {
Ok(settings) => (settings.daemon.log_level, None),
Err(error) => (None, Some(format!("{error:#}"))),
}
}
fn emit_bootstrap_warnings(rejected: &[&str], settings_error: Option<&str>) {
for source in rejected {
tracing::warn!(
source,
"invalid tracing directive; using next filter source"
);
}
if let Some(error) = settings_error {
omni_dev::utils::settings::Settings::warn_bootstrap_failure(error);
}
}
fn resolve_filter(
daemon_run: bool,
environment: Option<&str>,
daemon: Option<&str>,
) -> (tracing_subscriber::EnvFilter, Vec<&'static str>) {
let mut rejected = Vec::new();
for (source, directive) in [
("RUST_LOG", environment),
("daemon.log_level", daemon.filter(|_| daemon_run)),
] {
if let Some(directive) = directive {
match tracing_subscriber::EnvFilter::try_new(directive) {
Ok(filter) => return (filter, rejected),
Err(_) => rejected.push(source),
}
}
}
(
tracing_subscriber::EnvFilter::new(default_filter(daemon_run)),
rejected,
)
}
fn die(e: &anyhow::Error) -> ! {
eprintln!("Error: {e}");
let mut source = e.source();
while let Some(err) = source {
eprintln!(" Caused by: {err}");
source = err.source();
}
process::exit(1);
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
fn path(parts: &[&str]) -> Vec<String> {
parts.iter().map(|s| (*s).to_string()).collect()
}
#[test]
fn is_daemon_run_matches_only_daemon_run() {
assert!(is_daemon_run(&path(&["daemon", "run"])));
assert!(is_daemon_run(&path(&[
"daemon",
"run",
"--socket",
"/tmp/d.sock"
])));
assert!(!is_daemon_run(&path(&["daemon", "status"])));
assert!(!is_daemon_run(&path(&["daemon", "start"])));
assert!(!is_daemon_run(&path(&["daemon"])));
assert!(!is_daemon_run(&path(&["jira", "read"])));
assert!(!is_daemon_run(&[]));
}
#[test]
fn tracing_filter_precedence_and_fallbacks() {
for (daemon_run, env, daemon, expected, rejected) in [
(true, Some("trace"), Some("debug"), "trace", 0),
(true, None, Some("debug"), "debug", 0),
(true, None, None, "info", 0),
(false, None, Some("debug"), "warn", 0),
(true, Some("["), Some("debug"), "debug", 1),
(true, None, Some("["), "info", 1),
(false, Some("["), Some("debug"), "warn", 1),
] {
let (filter, errors) = resolve_filter(daemon_run, env, daemon);
assert_eq!(filter.to_string(), expected);
assert_eq!(errors.len(), rejected);
}
}
#[test]
fn a_failed_bootstrap_settings_load_is_carried_for_later_reporting() {
let (filter, error) = daemon_filter_from(Err(anyhow::anyhow!("settings.json is broken")));
assert_eq!(filter, None);
assert_eq!(error.as_deref(), Some("settings.json is broken"));
let settings: omni_dev::utils::settings::Settings =
serde_json::from_str(r#"{"daemon":{"log_level":"debug"}}"#).unwrap();
assert_eq!(
daemon_filter_from(Ok(settings)),
(Some("debug".to_string()), None)
);
}
#[test]
fn bootstrap_warnings_name_each_rejected_source_and_the_settings_failure() {
use std::io::{Read, Seek};
use std::sync::Arc;
let file = Arc::new(tempfile::tempfile().unwrap());
let subscriber = tracing_subscriber::fmt()
.with_max_level(tracing::Level::WARN)
.with_ansi(false)
.with_writer(Arc::clone(&file))
.finish();
tracing::subscriber::with_default(subscriber, || {
emit_bootstrap_warnings(&[], None);
assert_eq!(file.metadata().unwrap().len(), 0);
emit_bootstrap_warnings(
&["RUST_LOG", "daemon.log_level"],
Some("main-test bootstrap settings failure"),
);
});
let mut logs = String::new();
(&*file).rewind().unwrap();
(&*file).read_to_string(&mut logs).unwrap();
assert!(logs.contains("invalid tracing directive"), "{logs}");
assert!(logs.contains("RUST_LOG"), "{logs}");
assert!(logs.contains("daemon.log_level"), "{logs}");
assert!(
logs.contains("main-test bootstrap settings failure"),
"{logs}"
);
}
#[test]
fn default_filter_is_info_only_for_daemon_run() {
assert_eq!(default_filter(true), "info");
assert_eq!(default_filter(false), "warn");
}
}