Fall back to stdout if log file is inaccessible (#8415)

Dheeraj and Kirill Bulatov created

https://github.com/zed-industries/zed/assets/31967125/644f3524-e680-457c-bf4c-a7f11f3ec8db

Fixes #8209
Defaults to env logger in case of open/access failure.

Release Notes:

- Improved Zed behavior when no log file access is possible ([8209](https://github.com/zed-industries/zed/issues/8209))

---------

Co-authored-by: Kirill Bulatov <mail4score@gmail.com>

Change summary

crates/zed/src/main.rs | 80 +++++++++++++++++++++++++------------------
crates/zed/src/zed.rs  | 47 +++++++++++++++++--------
2 files changed, 79 insertions(+), 48 deletions(-)

Detailed changes

crates/zed/src/main.rs 🔗

@@ -497,30 +497,7 @@ fn init_paths() {
 
 fn init_logger() {
     if stdout_is_a_pty() {
-        Builder::new()
-            .parse_default_env()
-            .format(|buf, record| {
-                use env_logger::fmt::Color;
-
-                let subtle = buf
-                    .style()
-                    .set_color(Color::Black)
-                    .set_intense(true)
-                    .clone();
-                write!(buf, "{}", subtle.value("["))?;
-                write!(
-                    buf,
-                    "{} ",
-                    chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%:z")
-                )?;
-                write!(buf, "{:<5}", buf.default_styled_level(record.level()))?;
-                if let Some(path) = record.module_path() {
-                    write!(buf, " {}", path)?;
-                }
-                write!(buf, "{}", subtle.value("]"))?;
-                writeln!(buf, " {}", record.args())
-            })
-            .init();
+        init_stdout_logger();
     } else {
         let level = LevelFilter::Info;
 
@@ -533,21 +510,58 @@ fn init_logger() {
             let _ = std::fs::rename(&*paths::LOG, &*paths::OLD_LOG);
         }
 
-        let log_file = OpenOptions::new()
+        match OpenOptions::new()
             .create(true)
             .append(true)
             .open(&*paths::LOG)
-            .expect("could not open logfile");
-
-        let config = ConfigBuilder::new()
-            .set_time_format_str("%Y-%m-%dT%T%:z")
-            .set_time_to_local(true)
-            .build();
-
-        simplelog::WriteLogger::init(level, config, log_file).expect("could not initialize logger");
+        {
+            Ok(log_file) => {
+                let config = ConfigBuilder::new()
+                    .set_time_format_str("%Y-%m-%dT%T%:z")
+                    .set_time_to_local(true)
+                    .build();
+
+                simplelog::WriteLogger::init(level, config, log_file)
+                    .expect("could not initialize logger");
+            }
+            Err(err) => {
+                init_stdout_logger();
+                log::error!(
+                    "could not open log file, defaulting to stdout logging: {}",
+                    err
+                );
+            }
+        }
     }
 }
 
+fn init_stdout_logger() {
+    Builder::new()
+        .parse_default_env()
+        .format(|buf, record| {
+            use env_logger::fmt::Color;
+
+            let subtle = buf
+                .style()
+                .set_color(Color::Black)
+                .set_intense(true)
+                .clone();
+            write!(buf, "{}", subtle.value("["))?;
+            write!(
+                buf,
+                "{} ",
+                chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%:z")
+            )?;
+            write!(buf, "{:<5}", buf.default_styled_level(record.level()))?;
+            if let Some(path) = record.module_path() {
+                write!(buf, " {}", path)?;
+            }
+            write!(buf, "{}", subtle.value("]"))?;
+            writeln!(buf, " {}", record.args())
+        })
+        .init();
+}
+
 #[derive(Serialize, Deserialize)]
 struct LocationData {
     file: String,

crates/zed/src/zed.rs 🔗

@@ -477,25 +477,42 @@ fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
             cx.spawn(|workspace, mut cx| async move {
                 let (old_log, new_log) =
                     futures::join!(fs.load(&paths::OLD_LOG), fs.load(&paths::LOG));
-
-                let mut lines = VecDeque::with_capacity(MAX_LINES);
-                for line in old_log
-                    .iter()
-                    .flat_map(|log| log.lines())
-                    .chain(new_log.iter().flat_map(|log| log.lines()))
-                {
-                    if lines.len() == MAX_LINES {
-                        lines.pop_front();
+                let log = match (old_log, new_log) {
+                    (Err(_), Err(_)) => None,
+                    (old_log, new_log) => {
+                        let mut lines = VecDeque::with_capacity(MAX_LINES);
+                        for line in old_log
+                            .iter()
+                            .flat_map(|log| log.lines())
+                            .chain(new_log.iter().flat_map(|log| log.lines()))
+                        {
+                            if lines.len() == MAX_LINES {
+                                lines.pop_front();
+                            }
+                            lines.push_back(line);
+                        }
+                        Some(
+                            lines
+                                .into_iter()
+                                .flat_map(|line| [line, "\n"])
+                                .collect::<String>(),
+                        )
                     }
-                    lines.push_back(line);
-                }
-                let log = lines
-                    .into_iter()
-                    .flat_map(|line| [line, "\n"])
-                    .collect::<String>();
+                };
 
                 workspace
                     .update(&mut cx, |workspace, cx| {
+                        let Some(log) = log else {
+                            workspace.show_notification(29, cx, |cx| {
+                                cx.new_view(|_| {
+                                    MessageNotification::new(format!(
+                                        "Unable to access/open log file at path {:?}",
+                                        paths::LOG.as_path()
+                                    ))
+                                })
+                            });
+                            return;
+                        };
                         let project = workspace.project().clone();
                         let buffer = project
                             .update(cx, |project, cx| project.create_buffer("", None, cx))