1use crate::config::AppConfig;
10use std::fs::OpenOptions;
11use std::path::PathBuf;
12use tracing_subscriber::prelude::*;
13use tracing_subscriber::{filter::Directive, EnvFilter};
14
15fn get_log_file_path(working_dir: &PathBuf) -> Result<PathBuf, Box<dyn std::error::Error>> {
19 let logs_dir = working_dir.join(".robit").join("logs");
20
21 std::fs::create_dir_all(&logs_dir)?;
23
24 let date = chrono::Local::now().format("%Y-%m-%d");
26 let log_file = logs_dir.join(format!("robit-{}.log", date));
27
28 Ok(log_file)
29}
30
31fn build_filter(
33 app_config: Option<&AppConfig>,
34 target_crate: &str,
35 additional_directives: &[&str],
36) -> EnvFilter {
37 let mut filter = EnvFilter::from_default_env();
38
39 if std::env::var("RUST_LOG").is_err() {
41 let global_level = app_config
43 .and_then(|c| c.log_level.as_deref())
44 .unwrap_or("info");
45
46 if let Ok(dir) = format!("{}={}", target_crate, global_level).parse() {
48 filter = filter.add_directive(dir);
49 }
50
51 for robit_crate in &["robit_agent", "robit_chatbot", "robit_ai"] {
53 if robit_crate != &target_crate {
54 if let Ok(dir) = format!("{}={}", robit_crate, global_level).parse() {
55 filter = filter.add_directive(dir);
56 }
57 }
58 }
59
60 for dir_str in additional_directives {
62 if let Ok(dir) = dir_str.parse::<Directive>() {
63 filter = filter.add_directive(dir);
64 }
65 }
66
67 for dep_crate in &[
69 "reqwest",
70 "hyper",
71 "hyper_util",
72 "tungstenite",
73 "tokio_tungstenite",
74 "tokio",
75 "tauri",
76 ] {
77 if let Ok(dir) = format!("{}=warn", dep_crate).parse() {
78 filter = filter.add_directive(dir);
79 }
80 }
81 }
82
83 filter
84}
85
86pub fn init_logging(
102 app_config: Option<&AppConfig>,
103 target_crate: &str,
104 working_dir: &PathBuf,
105 additional_directives: &[&str],
106) {
107 let filter = build_filter(app_config, target_crate, additional_directives);
108
109 let log_file_enabled = app_config.and_then(|c| c.log_file).unwrap_or(false);
111
112 if log_file_enabled {
113 match get_log_file_path(working_dir) {
115 Ok(log_path) => {
116 match OpenOptions::new()
117 .create(true)
118 .append(true)
119 .open(&log_path)
120 {
121 Ok(file) => {
122 let file_writer = tracing_subscriber::fmt::writer::MakeWriterExt::with_max_level(file, tracing::Level::TRACE);
123
124 let console_layer = tracing_subscriber::fmt::layer()
126 .with_writer(std::io::stdout)
127 .with_filter(filter.clone());
128
129 let file_layer = tracing_subscriber::fmt::layer()
130 .with_writer(file_writer)
131 .with_ansi(false)
132 .with_filter(filter);
133
134 let registry = tracing_subscriber::registry()
136 .with(console_layer)
137 .with(file_layer);
138
139 registry.init();
140
141 tracing::info!("Logging to file: {}", log_path.display());
142 }
143 Err(e) => {
144 eprintln!("Failed to open log file: {}. Falling back to console-only logging.", e);
146 tracing_subscriber::fmt().with_env_filter(filter).init();
147 }
148 }
149 }
150 Err(e) => {
151 eprintln!("Failed to prepare log path: {}. Falling back to console-only logging.", e);
153 tracing_subscriber::fmt().with_env_filter(filter).init();
154 }
155 }
156 } else {
157 tracing_subscriber::fmt().with_env_filter(filter).init();
159 }
160}
161
162pub fn init_logging_silent(
166 app_config: Option<&AppConfig>,
167 target_crate: &str,
168 _working_dir: &PathBuf,
169 additional_directives: &[&str],
170) {
171 let filter = build_filter(app_config, target_crate, additional_directives);
172
173 tracing_subscriber::fmt()
174 .with_env_filter(filter)
175 .with_writer(std::io::sink)
176 .init();
177}