use std::sync::{Arc, OnceLock};
use tokio::sync::watch;
#[derive(Debug, Clone, Copy)]
pub enum LogLevel {
Verbose,
Debug,
Info,
Warn,
Error,
}
#[derive(Debug, Clone, Copy)]
pub enum LogTag {
Native, WebViewConsole, LxAppServiceConsole, }
impl LogTag {
pub fn as_str(&self) -> &'static str {
match self {
LogTag::Native => "Native",
LogTag::WebViewConsole => "JSView",
LogTag::LxAppServiceConsole => "JSService",
}
}
}
#[derive(Debug, Clone)]
pub struct LogMessage {
pub tag: LogTag,
pub level: LogLevel,
pub appid: Option<String>,
pub path: Option<String>,
pub message: String,
}
impl LogMessage {
fn new(tag: LogTag, message: impl std::fmt::Display) -> Self {
Self {
tag,
level: LogLevel::Info,
appid: None,
path: None,
message: message.to_string(),
}
}
}
static GLOBAL_LOG_MANAGER: OnceLock<Arc<LogManager>> = OnceLock::new();
pub struct LogManager {
sender: watch::Sender<LogMessage>,
logger: Box<dyn Fn(&LogMessage) + Send + Sync>,
}
impl LogManager {
pub fn init<F>(logger: F) -> Arc<Self>
where
F: Fn(&LogMessage) + Send + Sync + 'static,
{
GLOBAL_LOG_MANAGER
.get_or_init(|| {
let (sender, _receiver) = watch::channel(LogMessage {
tag: LogTag::Native,
level: LogLevel::Info,
appid: None,
path: None,
message: Default::default(),
});
Arc::new(LogManager {
sender,
logger: Box::new(logger),
})
})
.clone()
}
pub fn get() -> Option<Arc<Self>> {
GLOBAL_LOG_MANAGER.get().cloned()
}
pub fn subscribe(&self) -> watch::Receiver<LogMessage> {
self.sender.subscribe()
}
pub fn print_to_native(&self, message: &LogMessage) {
(self.logger)(message);
}
fn log(&self, message: LogMessage) {
if self.sender.receiver_count() > 0 {
let _ = self.sender.send(message.clone());
} else {
(self.logger)(&message);
}
}
}
pub fn log(tag: LogTag, level: LogLevel, message: impl std::fmt::Display) {
if let Some(manager) = GLOBAL_LOG_MANAGER.get() {
let log_message = LogMessage {
tag,
level,
appid: None,
path: None,
message: message.to_string(),
};
manager.log(log_message);
}
}
#[macro_export]
macro_rules! info {
($($arg:tt)*) => {
$crate::log::LogBuilder::new($crate::log::LogTag::Native, format!($($arg)*))
};
}
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => {
$crate::log::LogBuilder::new($crate::log::LogTag::Native, format!($($arg)*))
.with_level($crate::log::LogLevel::Warn)
};
}
#[macro_export]
macro_rules! error {
($($arg:tt)*) => {
$crate::log::LogBuilder::new($crate::log::LogTag::Native, format!($($arg)*))
.with_level($crate::log::LogLevel::Error)
};
}
#[macro_export]
macro_rules! debug {
($($arg:tt)*) => {
$crate::log::LogBuilder::new($crate::log::LogTag::Native, format!($($arg)*))
.with_level($crate::log::LogLevel::Debug)
};
}
#[macro_export]
macro_rules! verbose {
($($arg:tt)*) => {
$crate::log::LogBuilder::new($crate::log::LogTag::Native, format!($($arg)*))
.with_level($crate::log::LogLevel::Verbose)
};
}
pub struct LogBuilder {
message: LogMessage,
}
impl LogBuilder {
pub fn new(tag: LogTag, message: impl std::fmt::Display) -> Self {
Self {
message: LogMessage::new(tag, message),
}
}
pub fn with_appid(mut self, appid: impl Into<String>) -> Self {
self.message.appid = Some(appid.into());
self
}
pub fn with_path(mut self, path: impl Into<String>) -> Self {
self.message.path = Some(path.into());
self
}
pub fn with_level(mut self, level: LogLevel) -> Self {
self.message.level = level;
self
}
}
impl Drop for LogBuilder {
fn drop(&mut self) {
if let Some(manager) = GLOBAL_LOG_MANAGER.get() {
manager.log(self.message.clone());
}
}
}