use std::fmt;
use std::fs::{File, OpenOptions};
use std::io::{self, Write};
#[cfg(unix)]
use std::os::unix::fs::OpenOptionsExt;
use std::path::Path;
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, Ordering};
use anyhow::{Context, Result};
use tracing_subscriber::fmt::MakeWriter;
static LOG_FILE: Mutex<Option<File>> = Mutex::new(None);
static QUIET_WARNINGS: AtomicBool = AtomicBool::new(false);
pub fn set_quiet_warnings(quiet: bool) {
QUIET_WARNINGS.store(quiet, Ordering::Relaxed);
}
pub fn set_log_file(path: &Path) -> Result<()> {
let mut options = OpenOptions::new();
options.create(true).append(true);
#[cfg(unix)]
options.mode(0o600);
let file = options
.open(path)
.with_context(|| format!("Cannot open log file {}", path.display()))?;
let mut destination = LOG_FILE
.lock()
.map_err(|_| anyhow::anyhow!("diagnostic log lock is poisoned"))?;
*destination = Some(file);
Ok(())
}
pub fn has_log_file() -> bool {
LOG_FILE
.lock()
.map(|destination| destination.is_some())
.unwrap_or(false)
}
fn write(bytes: &[u8]) -> io::Result<()> {
let mut destination = LOG_FILE
.lock()
.map_err(|_| io::Error::other("diagnostic log lock is poisoned"))?;
if let Some(file) = destination.as_mut() {
file.write_all(bytes)?;
file.flush()
} else {
let mut stderr = io::stderr().lock();
stderr.write_all(bytes)?;
stderr.flush()
}
}
pub fn write_line(arguments: fmt::Arguments<'_>) {
let mut line = String::new();
if fmt::write(&mut line, arguments).is_ok() {
line.push('\n');
let _ = write(line.as_bytes());
}
}
pub fn write_warning_line(arguments: fmt::Arguments<'_>) {
if !QUIET_WARNINGS.load(Ordering::Relaxed) {
write_line(arguments);
}
}
#[derive(Debug, Default)]
pub struct DiagnosticWriter {
buffer: Vec<u8>,
}
impl Write for DiagnosticWriter {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
self.buffer.extend_from_slice(bytes);
Ok(bytes.len())
}
fn flush(&mut self) -> io::Result<()> {
if self.buffer.is_empty() {
return Ok(());
}
let result = write(&self.buffer);
if result.is_ok() {
self.buffer.clear();
}
result
}
}
impl Drop for DiagnosticWriter {
fn drop(&mut self) {
let _ = self.flush();
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DiagnosticMakeWriter;
impl<'a> MakeWriter<'a> for DiagnosticMakeWriter {
type Writer = DiagnosticWriter;
fn make_writer(&'a self) -> Self::Writer {
DiagnosticWriter::default()
}
}
#[macro_export]
macro_rules! diagnosticln {
($($argument:tt)*) => {
$crate::utils::diagnostics::write_line(format_args!($($argument)*))
};
}
#[macro_export]
macro_rules! warningln {
($($argument:tt)*) => {
$crate::utils::diagnostics::write_warning_line(format_args!($($argument)*))
};
}