use log::info;
use std::fs::{File, OpenOptions};
use std::io::Read;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::fs::{metadata, rename};
pub fn initialize_brz_log_filename() -> Result<PathBuf, std::io::Error> {
let brz_log = std::env::var("BRZ_LOG").ok();
if let Some(brz_log) = brz_log {
Ok(PathBuf::from(brz_log))
} else {
let cache_dir = crate::bedding::cache_dir()?;
Ok(cache_dir.join("brz.log"))
}
}
pub fn rollover_trace_maybe(trace_fname: &Path) -> io::Result<()> {
const MAX_LOG_SIZE: u64 = 4 * (1 << 20);
let size = metadata(trace_fname)?.len();
if size <= MAX_LOG_SIZE {
return Ok(());
}
let old_fname = trace_fname.with_extension("log.old");
rename(trace_fname, old_fname)?;
Ok(())
}
pub fn open_or_create_log_file<P: AsRef<Path>>(filename: P) -> std::io::Result<File> {
let mut flags = OpenOptions::new();
flags.append(true);
loop {
match flags.open(filename.as_ref()) {
Ok(fd) => return Ok(fd),
Err(e) if e.kind() == io::ErrorKind::NotFound => {
let mut flags = OpenOptions::new();
flags.create(true).truncate(true).write(true);
match flags.open(&filename) {
Ok(fd) => {
breezy_osutils::file::copy_ownership_from_path(&filename, None)?;
return Ok(fd);
}
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => continue,
Err(e) => return Err(e),
}
}
Err(e) => return Err(e),
}
}
}
static mut BRZ_LOG_FILENAME: Option<PathBuf> = None;
pub fn get_brz_log_filename() -> Option<PathBuf> {
unsafe { BRZ_LOG_FILENAME.clone() }
}
pub fn set_brz_log_filename(filename: Option<&Path>) {
unsafe { BRZ_LOG_FILENAME = filename.map(|p| p.to_path_buf()) };
}
pub fn open_brz_log() -> Option<File> {
let filename = initialize_brz_log_filename().ok()?;
unsafe { BRZ_LOG_FILENAME = Some(filename.clone()) };
rollover_trace_maybe(&filename).ok();
let mut brz_log_file = match open_or_create_log_file(&filename) {
Ok(fd) => fd,
Err(e) => {
eprintln!("failed to open trace file: {}: {}", filename.display(), e);
return None;
}
};
if brz_log_file
.metadata()
.ok()
.map_or(false, |md| md.len() == 0)
{
if let Err(e) = writeln!(
brz_log_file,
"this is a debug log for diagnosing/reporting problems in brz"
) {
eprintln!("failed to write to trace file: {}", e);
}
if let Err(e) = writeln!(
brz_log_file,
"you can delete or truncate this file, or include sections in"
) {
eprintln!("failed to write to trace file: {}", e);
}
if let Err(e) = writeln!(
brz_log_file,
"bug reports to https://bugs.launchpad.net/brz/+filebug"
) {
eprintln!("failed to write to trace file: {}", e);
}
}
Some(brz_log_file)
}
pub struct BreezyTraceLogger<F> {
file: Mutex<F>,
start_time: chrono::DateTime<chrono::Local>,
short: bool,
}
impl<F: Write> BreezyTraceLogger<F> {
pub fn new(mut file: F, short: bool) -> Self {
let start_time = chrono::Local::now();
if !short {
let start_time = breezy_osutils::time::format_local_date(
start_time.timestamp(),
None,
breezy_osutils::time::Timezone::Local,
None,
true,
);
file.write_all((start_time + "\n").as_bytes())
.expect("failed to write to trace file");
}
Self {
file: Mutex::new(file),
short,
start_time,
}
}
pub fn mutter(&self, msg: &str) {
let elapsed = chrono::Local::now().signed_duration_since(self.start_time);
let mut file = self.file.lock().unwrap();
writeln!(
file,
"{:.3} {msg}",
elapsed.num_seconds() as f64 + elapsed.num_microseconds().unwrap() as f64 / 1_000_000.0
)
.expect("failed to write to trace file");
file.flush().expect("failed to flush trace file");
}
}
impl Default for BreezyTraceLogger<File> {
fn default() -> Self {
let file = open_brz_log();
Self::new(
file.unwrap_or_else(|| panic!("failed to open trace file")),
false,
)
}
}
impl Default for BreezyTraceLogger<Box<dyn Write + Send>> {
fn default() -> Self {
let file = open_brz_log();
Self::new(
Box::new(file.unwrap_or_else(|| panic!("failed to open trace file"))),
false,
)
}
}
impl<F: Write + Send> log::Log for BreezyTraceLogger<F> {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= log::Level::Debug
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
let now = chrono::Local::now();
let level = match record.level() {
log::Level::Error => "ERROR",
log::Level::Warn => "WARNING",
log::Level::Info => "INFO",
log::Level::Debug => "DEBUG",
log::Level::Trace => "TRACE",
};
if self.short {
self.file
.lock()
.unwrap()
.write_all(format!("{:>8} {}\n", level, record.args()).as_bytes())
.expect("failed to write to trace file");
} else {
self.file
.lock()
.unwrap()
.write_all(
format!(
"[{:5}] {}.{:03} {}: {}\n",
std::process::id(),
now.format("%Y-%m-%d %H:%M:%S"),
now.timestamp_subsec_millis(),
level,
record.args()
)
.as_bytes(),
)
.expect("failed to write to trace file");
}
}
}
fn flush(&self) {
self.file.lock().unwrap().flush().ok();
}
}
struct BreezyStderrLogger {}
impl BreezyStderrLogger {
pub fn new() -> Self {
Self {}
}
}
impl Default for BreezyStderrLogger {
fn default() -> Self {
Self::new()
}
}
impl log::Log for BreezyStderrLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= log::Level::Info
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
eprintln!("{}", record.args());
}
}
fn flush(&self) {
std::io::stderr().flush().ok();
}
}
const SHORT_FIELDS: [&str; 3] = ["VmPeak", "VmSize", "VmRSS"];
#[cfg(unix)]
pub fn debug_memory_proc(message: &str, short: bool) {
if let Ok(mut status_file) = File::open(format!("/proc/{}/status", std::process::id())) {
let mut status = String::new();
if status_file.read_to_string(&mut status).is_ok() {
if !message.is_empty() {
info!("{}", message);
}
for line in status.lines() {
if !short {
info!("{}", line);
} else {
for field in &SHORT_FIELDS {
if line.starts_with(field) {
info!("{}", line);
break;
}
}
}
}
}
}
}