logging: Add runtime log replace upon max size limit (#25768)

Closes #25638

We currently only check the log size limit at startup and move `Zed.log`
to `Zed.log.old`. If a user runs Zed for an extended period, there's no
runtime restriction on the log file size, which can cause it to grow to
several gigabytes.

This PR fixes that by tracking the log file size while writing. If it
exceeds a certain threshold, we perform the same log replace and
continue logging.

Release Notes:

- Fixed an issue where `Zed.log` could grow excessively large during
long sessions of Zed.
This commit is contained in:
smit 2025-02-28 12:23:30 +05:30 committed by GitHub
parent 472dde509f
commit 604eb91a6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 130 additions and 84 deletions

122
crates/zed/src/logger.rs Normal file
View file

@ -0,0 +1,122 @@
use chrono::Offset;
use env_logger::Builder;
use log::LevelFilter;
use simplelog::ConfigBuilder;
use std::fs::{self, File, OpenOptions};
use std::io::{self, Write};
use time::UtcOffset;
pub fn init_logger() {
let level = LevelFilter::Info;
// Prevent log file from becoming too large.
const KIB: u64 = 1024;
const MIB: u64 = 1024 * KIB;
const MAX_LOG_BYTES: u64 = MIB;
if std::fs::metadata(paths::log_file()).map_or(false, |metadata| metadata.len() > MAX_LOG_BYTES)
{
let _ = std::fs::rename(paths::log_file(), paths::old_log_file());
}
match LogWriter::new(MAX_LOG_BYTES) {
Ok(writer) => {
let mut config_builder = ConfigBuilder::new();
config_builder.set_time_format_rfc3339();
let local_offset = chrono::Local::now().offset().fix().local_minus_utc();
if let Ok(offset) = UtcOffset::from_whole_seconds(local_offset) {
config_builder.set_time_offset(offset);
}
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
{
config_builder.add_filter_ignore_str("zbus");
config_builder.add_filter_ignore_str("blade_graphics::hal::resource");
config_builder.add_filter_ignore_str("naga::back::spv::writer");
}
let config = config_builder.build();
simplelog::WriteLogger::init(level, config, writer)
.expect("could not initialize logger");
}
Err(err) => {
init_stdout_logger();
log::error!(
"could not open log file, defaulting to stdout logging: {}",
err
);
}
}
}
pub fn init_stdout_logger() {
Builder::new()
.parse_default_env()
.format(|buf, record| {
use env_logger::fmt::style::{AnsiColor, Style};
let subtle = Style::new().fg_color(Some(AnsiColor::BrightBlack.into()));
write!(buf, "{subtle}[{subtle:#}")?;
write!(
buf,
"{} ",
chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%:z")
)?;
let level_style = buf.default_level_style(record.level());
write!(buf, "{level_style}{:<5}{level_style:#}", record.level())?;
if let Some(path) = record.module_path() {
write!(buf, " {path}")?;
}
write!(buf, "{subtle}]{subtle:#}")?;
writeln!(buf, " {}", record.args())
})
.init();
}
struct LogWriter {
file: File,
max_size: u64,
current_size: u64,
}
impl LogWriter {
fn new(max_size: u64) -> io::Result<Self> {
let file = OpenOptions::new()
.create(true)
.append(true)
.open(paths::log_file())?;
let current_size = file.metadata()?.len();
Ok(LogWriter {
file,
max_size,
current_size,
})
}
fn replace(&mut self) -> io::Result<()> {
self.file.sync_all()?;
fs::rename(paths::log_file(), paths::old_log_file())?;
self.file = OpenOptions::new()
.create(true)
.append(true)
.open(paths::log_file())?;
self.current_size = 0;
Ok(())
}
}
impl Write for LogWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if self.current_size + buf.len() as u64 > self.max_size {
self.replace()?;
}
let bytes = self.file.write(buf)?;
self.current_size += bytes as u64;
Ok(bytes)
}
fn flush(&mut self) -> io::Result<()> {
self.file.flush()
}
}

View file

@ -1,11 +1,11 @@
// Disable command line from opening on release mode
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod logger;
mod reliability;
mod zed;
use anyhow::{anyhow, Context as _, Result};
use chrono::Offset;
use clap::{command, Parser};
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
use client::{parse_zed_link, Client, ProxySettings, UserStore};
@ -13,7 +13,6 @@ use collab_ui::channel_view::ChannelView;
use collections::HashMap;
use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
use editor::Editor;
use env_logger::Builder;
use extension::ExtensionHostProxy;
use extension_host::ExtensionStore;
use fs::{Fs, RealFs};
@ -24,11 +23,11 @@ use gpui::{App, AppContext as _, Application, AsyncApp, UpdateGlobal as _};
use gpui_tokio::Tokio;
use http_client::{read_proxy_from_env, Uri};
use language::LanguageRegistry;
use log::LevelFilter;
use prompt_library::PromptBuilder;
use reqwest_client::ReqwestClient;
use assets::Assets;
use logger::{init_logger, init_stdout_logger};
use node_runtime::{NodeBinaryOptions, NodeRuntime};
use parking_lot::Mutex;
use project::project_settings::ProjectSettings;
@ -36,11 +35,9 @@ use recent_projects::{open_ssh_project, SshSettings};
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
use session::{AppSession, Session};
use settings::{watch_config_file, Settings, SettingsStore};
use simplelog::ConfigBuilder;
use std::{
env,
fs::OpenOptions,
io::{self, IsTerminal, Write},
io::{self, IsTerminal},
path::{Path, PathBuf},
process,
sync::Arc,
@ -49,7 +46,6 @@ use theme::{
ActiveTheme, IconThemeNotFoundError, SystemAppearance, ThemeNotFoundError, ThemeRegistry,
ThemeSettings,
};
use time::UtcOffset;
use util::{maybe, ResultExt, TryFutureExt};
use uuid::Uuid;
use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN};
@ -198,7 +194,11 @@ fn main() {
return;
}
init_logger();
if stdout_is_a_pty() {
init_stdout_logger();
} else {
init_logger();
}
log::info!("========== starting zed ==========");
@ -936,82 +936,6 @@ fn init_paths() -> HashMap<io::ErrorKind, Vec<&'static Path>> {
})
}
fn init_logger() {
if stdout_is_a_pty() {
init_stdout_logger();
} else {
let level = LevelFilter::Info;
// Prevent log file from becoming too large.
const KIB: u64 = 1024;
const MIB: u64 = 1024 * KIB;
const MAX_LOG_BYTES: u64 = MIB;
if std::fs::metadata(paths::log_file())
.map_or(false, |metadata| metadata.len() > MAX_LOG_BYTES)
{
let _ = std::fs::rename(paths::log_file(), paths::old_log_file());
}
match OpenOptions::new()
.create(true)
.append(true)
.open(paths::log_file())
{
Ok(log_file) => {
let mut config_builder = ConfigBuilder::new();
config_builder.set_time_format_rfc3339();
let local_offset = chrono::Local::now().offset().fix().local_minus_utc();
if let Ok(offset) = UtcOffset::from_whole_seconds(local_offset) {
config_builder.set_time_offset(offset);
}
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
{
config_builder.add_filter_ignore_str("zbus");
config_builder.add_filter_ignore_str("blade_graphics::hal::resource");
config_builder.add_filter_ignore_str("naga::back::spv::writer");
}
let config = config_builder.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::style::{AnsiColor, Style};
let subtle = Style::new().fg_color(Some(AnsiColor::BrightBlack.into()));
write!(buf, "{subtle}[{subtle:#}")?;
write!(
buf,
"{} ",
chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%:z")
)?;
let level_style = buf.default_level_style(record.level());
write!(buf, "{level_style}{:<5}{level_style:#}", record.level())?;
if let Some(path) = record.module_path() {
write!(buf, " {path}")?;
}
write!(buf, "{subtle}]{subtle:#}")?;
writeln!(buf, " {}", record.args())
})
.init();
}
fn stdout_is_a_pty() -> bool {
std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && io::stdout().is_terminal()
}