Gist is we only need to block the foreground thread for reparsing if immediate language changes are useful to the user. That is usually only the case when they edit the buffer Release Notes: - Improved performance of large project searches and project diffs Co-authored by: David Kleingeld <david@zed.dev>
402 lines
11 KiB
Rust
402 lines
11 KiB
Rust
//! # logger
|
|
pub use log as log_impl;
|
|
|
|
mod env_config;
|
|
pub mod filter;
|
|
pub mod sink;
|
|
|
|
pub use sink::{flush, init_output_file, init_output_stderr, init_output_stdout};
|
|
|
|
pub const SCOPE_DEPTH_MAX: usize = 4;
|
|
|
|
pub fn init() {
|
|
if let Err(err) = try_init(None) {
|
|
log::error!("{err}");
|
|
eprintln!("{err}");
|
|
}
|
|
}
|
|
|
|
pub fn try_init(filter: Option<String>) -> anyhow::Result<()> {
|
|
log::set_logger(&ZLOG)?;
|
|
log::set_max_level(log::LevelFilter::max());
|
|
process_env(filter);
|
|
filter::refresh_from_settings(&std::collections::HashMap::default());
|
|
Ok(())
|
|
}
|
|
|
|
pub fn init_test() {
|
|
if get_env_config().is_some() && try_init(None).is_ok() {
|
|
init_output_stdout();
|
|
}
|
|
}
|
|
|
|
fn get_env_config() -> Option<String> {
|
|
std::env::var("ZED_LOG")
|
|
.or_else(|_| std::env::var("RUST_LOG"))
|
|
.ok()
|
|
.or_else(|| {
|
|
if std::env::var("CI").is_ok() {
|
|
Some("info".to_owned())
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn process_env(filter: Option<String>) {
|
|
let Some(env_config) = get_env_config().or(filter) else {
|
|
return;
|
|
};
|
|
match env_config::parse(&env_config) {
|
|
Ok(filter) => {
|
|
filter::init_env_filter(filter);
|
|
}
|
|
Err(err) => {
|
|
eprintln!("Failed to parse log filter: {}", err);
|
|
}
|
|
}
|
|
}
|
|
|
|
static ZLOG: Zlog = Zlog {};
|
|
|
|
pub struct Zlog {}
|
|
|
|
impl log::Log for Zlog {
|
|
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
|
filter::is_possibly_enabled_level(metadata.level())
|
|
}
|
|
|
|
fn log(&self, record: &log::Record) {
|
|
if !self.enabled(record.metadata()) {
|
|
return;
|
|
}
|
|
let (crate_name_scope, module_scope) = match record.module_path_static() {
|
|
Some(module_path) => {
|
|
let crate_name = private::extract_crate_name_from_module_path(module_path);
|
|
let crate_name_scope = private::scope_new(&[crate_name]);
|
|
let module_scope = private::scope_new(&[module_path]);
|
|
(crate_name_scope, module_scope)
|
|
}
|
|
// TODO: when do we hit this
|
|
None => (private::scope_new(&[]), private::scope_new(&["*unknown*"])),
|
|
};
|
|
let level = record.metadata().level();
|
|
if !filter::is_scope_enabled(&crate_name_scope, Some(record.target()), level) {
|
|
return;
|
|
}
|
|
sink::submit(sink::Record {
|
|
scope: module_scope,
|
|
level,
|
|
message: record.args(),
|
|
// PERF(batching): store non-static paths in a cache + leak them and pass static str here
|
|
module_path: record.module_path().or(record.file()),
|
|
line: record.line(),
|
|
});
|
|
}
|
|
|
|
fn flush(&self) {
|
|
sink::flush();
|
|
}
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! log {
|
|
($logger:expr, $level:expr, $($arg:tt)+) => {
|
|
let level = $level;
|
|
let logger = $logger;
|
|
let enabled = $crate::filter::is_scope_enabled(&logger.scope, Some(module_path!()), level);
|
|
if enabled {
|
|
$crate::sink::submit($crate::sink::Record {
|
|
scope: logger.scope,
|
|
level,
|
|
message: &format_args!($($arg)+),
|
|
module_path: Some(module_path!()),
|
|
line: Some(line!()),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! trace {
|
|
($logger:expr => $($arg:tt)+) => {
|
|
$crate::log!($logger, $crate::log_impl::Level::Trace, $($arg)+);
|
|
};
|
|
($($arg:tt)+) => {
|
|
$crate::log!($crate::default_logger!(), $crate::log_impl::Level::Trace, $($arg)+);
|
|
};
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! debug {
|
|
($logger:expr => $($arg:tt)+) => {
|
|
$crate::log!($logger, $crate::log_impl::Level::Debug, $($arg)+);
|
|
};
|
|
($($arg:tt)+) => {
|
|
$crate::log!($crate::default_logger!(), $crate::log_impl::Level::Debug, $($arg)+);
|
|
};
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! info {
|
|
($logger:expr => $($arg:tt)+) => {
|
|
$crate::log!($logger, $crate::log_impl::Level::Info, $($arg)+);
|
|
};
|
|
($($arg:tt)+) => {
|
|
$crate::log!($crate::default_logger!(), $crate::log_impl::Level::Info, $($arg)+);
|
|
};
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! warn {
|
|
($logger:expr => $($arg:tt)+) => {
|
|
$crate::log!($logger, $crate::log_impl::Level::Warn, $($arg)+);
|
|
};
|
|
($($arg:tt)+) => {
|
|
$crate::log!($crate::default_logger!(), $crate::log_impl::Level::Warn, $($arg)+);
|
|
};
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! error {
|
|
($logger:expr => $($arg:tt)+) => {
|
|
$crate::log!($logger, $crate::log_impl::Level::Error, $($arg)+);
|
|
};
|
|
($($arg:tt)+) => {
|
|
$crate::log!($crate::default_logger!(), $crate::log_impl::Level::Error, $($arg)+);
|
|
};
|
|
}
|
|
|
|
/// Creates a timer that logs the duration it was active for either when
|
|
/// it is dropped, or when explicitly stopped using the `end` method.
|
|
/// Logs at the `trace` level.
|
|
/// Note that it will include time spent across await points
|
|
/// (i.e. should not be used to measure the performance of async code)
|
|
/// However, this is a feature not a bug, as it allows for a more accurate
|
|
/// understanding of how long the action actually took to complete, including
|
|
/// interruptions, which can help explain why something may have timed out,
|
|
/// why it took longer to complete than it would have had the await points resolved
|
|
/// immediately, etc.
|
|
#[macro_export]
|
|
macro_rules! time {
|
|
($logger:expr => $name:expr) => {
|
|
$crate::Timer::new($logger, $name)
|
|
};
|
|
($name:expr) => {
|
|
$crate::time!($crate::default_logger!() => $name)
|
|
};
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! scoped {
|
|
($parent:expr => $name:expr) => {{
|
|
$crate::scoped_logger($parent, $name)
|
|
}};
|
|
($name:expr) => {
|
|
$crate::scoped!($crate::default_logger!() => $name)
|
|
};
|
|
}
|
|
|
|
pub const fn scoped_logger(parent: Logger, name: &'static str) -> Logger {
|
|
let mut scope = parent.scope;
|
|
let mut index = 1; // always have crate/module name
|
|
while index < scope.len() && !scope[index].is_empty() {
|
|
index += 1;
|
|
}
|
|
if index >= scope.len() {
|
|
#[cfg(debug_assertions)]
|
|
{
|
|
panic!("Scope overflow trying to add scope... ignoring scope");
|
|
}
|
|
}
|
|
scope[index] = name;
|
|
Logger { scope }
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! default_logger {
|
|
() => {
|
|
$crate::Logger {
|
|
scope: $crate::private::scope_new(&[$crate::crate_name!()]),
|
|
}
|
|
};
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! crate_name {
|
|
() => {
|
|
$crate::private::extract_crate_name_from_module_path(module_path!())
|
|
};
|
|
}
|
|
|
|
/// functions that are used in macros, and therefore must be public,
|
|
/// but should not be used directly
|
|
pub mod private {
|
|
use super::*;
|
|
|
|
pub const fn extract_crate_name_from_module_path(module_path: &str) -> &str {
|
|
let mut i = 0;
|
|
let mod_path_bytes = module_path.as_bytes();
|
|
let mut index = mod_path_bytes.len();
|
|
while i + 1 < mod_path_bytes.len() {
|
|
if mod_path_bytes[i] == b':' && mod_path_bytes[i + 1] == b':' {
|
|
index = i;
|
|
break;
|
|
}
|
|
i += 1;
|
|
}
|
|
let Some((crate_name, _)) = module_path.split_at_checked(index) else {
|
|
return module_path;
|
|
};
|
|
crate_name
|
|
}
|
|
|
|
pub const fn scope_new(scopes: &[&'static str]) -> Scope {
|
|
assert!(scopes.len() <= SCOPE_DEPTH_MAX);
|
|
let mut scope = [""; SCOPE_DEPTH_MAX];
|
|
let mut i = 0;
|
|
while i < scopes.len() {
|
|
scope[i] = scopes[i];
|
|
i += 1;
|
|
}
|
|
scope
|
|
}
|
|
|
|
pub fn scope_alloc_new(scopes: &[&str]) -> ScopeAlloc {
|
|
assert!(scopes.len() <= SCOPE_DEPTH_MAX);
|
|
let mut scope = [""; SCOPE_DEPTH_MAX];
|
|
scope[0..scopes.len()].copy_from_slice(scopes);
|
|
scope.map(|s| s.to_string())
|
|
}
|
|
|
|
pub fn scope_to_alloc(scope: &Scope) -> ScopeAlloc {
|
|
scope.map(|s| s.to_string())
|
|
}
|
|
}
|
|
|
|
pub type Scope = [&'static str; SCOPE_DEPTH_MAX];
|
|
pub type ScopeAlloc = [String; SCOPE_DEPTH_MAX];
|
|
const SCOPE_STRING_SEP_STR: &str = ".";
|
|
const SCOPE_STRING_SEP_CHAR: char = '.';
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub struct Logger {
|
|
pub scope: Scope,
|
|
}
|
|
|
|
impl log::Log for Logger {
|
|
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
|
filter::is_possibly_enabled_level(metadata.level())
|
|
}
|
|
|
|
fn log(&self, record: &log::Record) {
|
|
if !self.enabled(record.metadata()) {
|
|
return;
|
|
}
|
|
let level = record.metadata().level();
|
|
if !filter::is_scope_enabled(&self.scope, Some(record.target()), level) {
|
|
return;
|
|
}
|
|
sink::submit(sink::Record {
|
|
scope: self.scope,
|
|
level,
|
|
message: record.args(),
|
|
module_path: record.module_path(),
|
|
line: record.line(),
|
|
});
|
|
}
|
|
|
|
fn flush(&self) {
|
|
sink::flush();
|
|
}
|
|
}
|
|
|
|
pub struct Timer {
|
|
pub logger: Logger,
|
|
pub start_time: std::time::Instant,
|
|
pub name: &'static str,
|
|
pub warn_if_longer_than: Option<std::time::Duration>,
|
|
pub done: bool,
|
|
}
|
|
|
|
impl Drop for Timer {
|
|
fn drop(&mut self) {
|
|
self.finish();
|
|
}
|
|
}
|
|
|
|
impl Timer {
|
|
#[must_use = "Timer will stop when dropped, the result of this function should be saved in a variable prefixed with `_` if it should stop when dropped"]
|
|
pub fn new(logger: Logger, name: &'static str) -> Self {
|
|
Self {
|
|
logger,
|
|
name,
|
|
start_time: std::time::Instant::now(),
|
|
warn_if_longer_than: None,
|
|
done: false,
|
|
}
|
|
}
|
|
|
|
pub fn warn_if_gt(mut self, warn_limit: std::time::Duration) -> Self {
|
|
self.warn_if_longer_than = Some(warn_limit);
|
|
self
|
|
}
|
|
|
|
pub fn end(mut self) {
|
|
self.finish();
|
|
}
|
|
|
|
fn finish(&mut self) {
|
|
if self.done {
|
|
return;
|
|
}
|
|
let elapsed = self.start_time.elapsed();
|
|
if let Some(warn_limit) = self.warn_if_longer_than
|
|
&& elapsed > warn_limit
|
|
{
|
|
crate::warn!(
|
|
self.logger =>
|
|
"Timer '{}' took {:?}. Which was longer than the expected limit of {:?}",
|
|
self.name,
|
|
elapsed,
|
|
warn_limit
|
|
);
|
|
self.done = true;
|
|
return;
|
|
}
|
|
crate::trace!(
|
|
self.logger =>
|
|
"Timer '{}' finished in {:?}",
|
|
self.name,
|
|
elapsed
|
|
);
|
|
self.done = true;
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_crate_name() {
|
|
assert_eq!(crate_name!(), "zlog");
|
|
assert_eq!(
|
|
private::extract_crate_name_from_module_path("my_speedy_⚡️_crate::some_module"),
|
|
"my_speedy_⚡️_crate"
|
|
);
|
|
assert_eq!(
|
|
private::extract_crate_name_from_module_path("my_speedy_crate_⚡️::some_module"),
|
|
"my_speedy_crate_⚡️"
|
|
);
|
|
assert_eq!(
|
|
private::extract_crate_name_from_module_path("my_speedy_crate_:⚡️:some_module"),
|
|
"my_speedy_crate_:⚡️:some_module"
|
|
);
|
|
assert_eq!(
|
|
private::extract_crate_name_from_module_path("my_speedy_crate_::⚡️some_module"),
|
|
"my_speedy_crate_"
|
|
);
|
|
}
|
|
}
|