#![allow(non_upper_case_globals)]
use log::{Level, LevelFilter, Metadata, Record};
#[derive(Copy, Clone)]
pub struct WinDbgLogger {
level: LevelFilter,
_priv: (),
}
pub static WINDBG_LOGGER: WinDbgLogger = WinDbgLogger {
level: LevelFilter::Trace,
_priv: (),
};
pub static WINDBG_L1: WinDbgLogger = WinDbgLogger {
level: LevelFilter::Error,
_priv: (),
};
pub static WINDBG_L2: WinDbgLogger = WinDbgLogger {
level: LevelFilter::Warn,
_priv: (),
};
pub static WINDBG_L3: WinDbgLogger = WinDbgLogger {
level: LevelFilter::Info,
_priv: (),
};
pub static WINDBG_L4: WinDbgLogger = WinDbgLogger {
level: LevelFilter::Debug,
_priv: (),
};
pub static WINDBG_L5: WinDbgLogger = WinDbgLogger {
level: LevelFilter::Trace,
_priv: (),
};
pub static WINDBG_L0: WinDbgLogger = WinDbgLogger {
level: LevelFilter::Off,
_priv: (),
};
#[cfg(all(target_os = "windows", feature = "gui"))]
pub fn windbg_simple_combo(
log_lvl: LevelFilter,
noti_lvl: LevelFilter,
) -> Box<dyn simplelog::SharedLogger> {
set_noti_lvl(noti_lvl);
match log_lvl {
LevelFilter::Error => Box::new(WINDBG_L1),
LevelFilter::Warn => Box::new(WINDBG_L2),
LevelFilter::Info => Box::new(WINDBG_L3),
LevelFilter::Debug => Box::new(WINDBG_L4),
LevelFilter::Trace => Box::new(WINDBG_L5),
LevelFilter::Off => Box::new(WINDBG_L0),
}
}
#[cfg(all(target_os = "windows", feature = "gui"))]
impl simplelog::SharedLogger for WinDbgLogger {
fn level(&self) -> LevelFilter {
self.level
}
fn config(&self) -> Option<&simplelog::Config> {
None
}
fn as_log(self: Box<Self>) -> Box<dyn log::Log> {
Box::new(*self)
}
}
pub fn iconify(lvl: log::Level) -> char {
match lvl {
Level::Error => '❗',
Level::Warn => '⚠',
Level::Info => 'ⓘ',
Level::Debug => 'ⓓ',
Level::Trace => 'ⓣ',
}
}
use std::sync::OnceLock;
pub fn is_thread_state() -> &'static bool {
set_thread_state(false)
}
pub fn set_thread_state(is: bool) -> &'static bool {
static CELL: OnceLock<bool> = OnceLock::new();
CELL.get_or_init(|| is)
}
pub fn get_noti_lvl() -> &'static LevelFilter {
set_noti_lvl(LevelFilter::Off)
}
pub fn set_noti_lvl(lvl: LevelFilter) -> &'static LevelFilter {
static CELL: OnceLock<LevelFilter> = OnceLock::new();
CELL.get_or_init(|| lvl)
}
use regex::Regex;
macro_rules! regex {
($re:literal $(,)?) => {{
static RE: OnceLock<regex::Regex> = OnceLock::new();
RE.get_or_init(|| regex::Regex::new($re).unwrap())
}};
}
fn clean_name(path: Option<&str>) -> String {
let re_ext: &Regex = regex!(r"\..*$"); let re_src: &Regex = regex!(r"src[\\/]");
if let Some(p) = path {
re_src.replace(&re_ext.replace(p, ""), "").to_string()
} else {
"?".to_string()
}
}
#[cfg(target_os = "windows")]
use winapi::um::processthreadsapi::GetCurrentThreadId;
impl log::Log for WinDbgLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= self.level
}
fn log(&self, record: &Record) {
#[cfg(not(target_os = "windows"))]
let thread_id = "";
#[cfg(target_os = "windows")]
let thread_id = if *is_thread_state() {
format!("{}¦", unsafe { GetCurrentThreadId() })
} else {
"".to_string()
};
if self.enabled(record.metadata()) {
let s = format!(
"{}{}{}:{} {}",
thread_id,
iconify(record.level()),
clean_name(record.file()),
record.line().unwrap_or(0),
record.args()
);
#[cfg(all(target_os = "windows", feature = "gui"))]
{
use crate::gui::win::*;
let title = format!(
"{}{}:{}",
thread_id,
clean_name(record.file()),
record.line().unwrap_or(0)
);
let msg = format!("{}", record.args());
if record.level() <= *get_noti_lvl() {
show_err_msg_nofail(title, msg);
}
}
output_debug_string(&s);
}
}
fn flush(&self) {}
}
pub fn output_debug_string(s: &str) {
#[cfg(windows)]
{
let len = s.encode_utf16().count() + 1;
let mut s_utf16: Vec<u16> = Vec::with_capacity(len);
s_utf16.extend(s.encode_utf16());
s_utf16.push(0);
unsafe {
OutputDebugStringW(&s_utf16[0]);
}
}
#[cfg(not(windows))]
{
let _ = s;
}
}
#[cfg(windows)]
unsafe extern "system" {
fn OutputDebugStringW(chars: *const u16);
fn IsDebuggerPresent() -> i32;
}
pub fn is_debugger_present() -> bool {
#[cfg(windows)]
{
unsafe { IsDebuggerPresent() != 0 }
}
#[cfg(not(windows))]
{
false
}
}
pub fn init() {
match log::set_logger(&WINDBG_LOGGER) {
Ok(()) => {} Err(_) => {
output_debug_string(
"Warning: Failed to register WinDbgLogger as the current Rust logger.\r\n",
);
}
}
}
macro_rules! define_init_at_level {
($func:ident, $level:ident) => {
#[unsafe(no_mangle)]
pub extern "C" fn $func() {
init();
log::set_max_level(LevelFilter::$level);
}
};
}
define_init_at_level!(rust_win_dbg_logger_init_trace, Trace);
define_init_at_level!(rust_win_dbg_logger_init_info, Info);
define_init_at_level!(rust_win_dbg_logger_init_debug, Debug);
define_init_at_level!(rust_win_dbg_logger_init_warn, Warn);
define_init_at_level!(rust_win_dbg_logger_init_error, Error);