diff --git a/veilid-core/src/api_logger.rs b/veilid-core/src/api_logger.rs new file mode 100644 index 00000000..acac2547 --- /dev/null +++ b/veilid-core/src/api_logger.rs @@ -0,0 +1,141 @@ +use crate::intf::*; +use crate::veilid_api::*; +use crate::veilid_core::*; +use crate::xx::*; +use log::{set_boxed_logger, set_max_level, Level, LevelFilter, Log, Metadata, Record}; +use once_cell::sync::OnceCell; + +struct ApiLoggerInner { + level: LevelFilter, + filter_ignore: Cow<'static, [Cow<'static, str>]>, + _join_handle: JoinHandle<()>, + tx: async_channel::Sender<(VeilidLogLevel, String)>, +} + +#[derive(Clone)] +pub struct ApiLogger { + inner: Arc>>, +} + +static API_LOGGER: OnceCell = OnceCell::new(); + +impl ApiLogger { + fn new_inner(level: LevelFilter, update_callback: UpdateCallback) -> ApiLoggerInner { + let (tx, rx) = async_channel::unbounded::<(VeilidLogLevel, String)>(); + let _join_handle: JoinHandle<()> = spawn(async move { + loop { + match rx.recv().await { + Ok(v) => { + (update_callback)(VeilidUpdate::Log(v.0, v.1)).await; + } + Err(_) => { + // Nothing to be done here... + break; + } + } + } + }); + ApiLoggerInner { + level, + filter_ignore: Default::default(), + _join_handle, + tx, + } + } + + pub fn init(log_level: LevelFilter, update_callback: UpdateCallback) { + set_max_level(log_level); + let api_logger = API_LOGGER.get_or_init(|| { + let api_logger = ApiLogger { + inner: Arc::new(Mutex::new(None)), + }; + set_boxed_logger(Box::new(api_logger.clone())).expect("failed to set api logger"); + api_logger + }); + + let mut inner = api_logger.inner.lock(); + *inner = Some(Self::new_inner(log_level, update_callback)); + } + + pub fn terminate() { + if let Some(api_logger) = API_LOGGER.get() { + let mut inner = api_logger.inner.lock(); + *inner = None; + set_max_level(LevelFilter::Off); + } + } + + pub fn change_log_level(log_level: LevelFilter) { + if let Some(api_logger) = API_LOGGER.get() { + if let Some(inner) = &mut *api_logger.inner.lock() { + set_max_level(log_level); + inner.level = log_level; + } + } + } + + pub fn add_filter_ignore_str(filter_ignore: &'static str) { + if let Some(api_logger) = API_LOGGER.get() { + if let Some(inner) = &mut *api_logger.inner.lock() { + let mut list = Vec::from(&*inner.filter_ignore); + list.push(Cow::Borrowed(filter_ignore)); + inner.filter_ignore = Cow::Owned(list); + } + } + } +} + +impl Log for ApiLogger { + fn enabled(&self, metadata: &Metadata<'_>) -> bool { + if let Some(inner) = &mut *self.inner.lock() { + return metadata.level() <= inner.level; + } + false + } + + fn log(&self, record: &Record<'_>) { + if let Some(inner) = &mut *self.inner.lock() { + // Skip filtered targets + let skip = match (record.target(), &*inner.filter_ignore) { + (path, ignore) if !ignore.is_empty() => { + // Check that the module path does not match any ignore filters + ignore.iter().any(|v| path.starts_with(&**v)) + } + _ => false, + }; + if skip { + return; + } + + let metadata = record.metadata(); + let level = metadata.level(); + if level <= inner.level { + let ll = VeilidLogLevel::from_log_level(level); + + let file = record.file().unwrap_or(""); + let loc = if level >= Level::Debug { + if let Some(line) = record.line() { + format!("[{}:{}] ", file, line) + } else { + format!("[{}:] ", file) + } + } else { + "".to_owned() + }; + let tgt = if record.target().is_empty() { + "".to_owned() + } else { + format!("{}: ", record.target()) + }; + + let s = format!("{}{}{}", tgt, loc, record.args()); + + let _ = inner.tx.try_send((ll, s)); + } + } + } + + fn flush(&self) { + // always flushes + } +} diff --git a/veilid-core/src/lib.rs b/veilid-core/src/lib.rs index 7b0e5c70..103541d3 100644 --- a/veilid-core/src/lib.rs +++ b/veilid-core/src/lib.rs @@ -4,6 +4,7 @@ #[macro_use] extern crate alloc; +mod api_logger; mod attachment_manager; mod callback_state_machine; mod connection_manager; @@ -45,3 +46,14 @@ pub fn veilid_version() -> (u32, u32, u32) { u32::from_str(env!("CARGO_PKG_VERSION_PATCH")).unwrap(), ) } + +pub static DEFAULT_LOG_IGNORE_LIST: [&'static str; 8] = [ + "async_std", + "async_io", + "polling", + "rustls", + "async_tungstenite", + "tungstenite", + "netlink_proto", + "netlink_sys", +]; diff --git a/veilid-core/src/tests/common/test_veilid_config.rs b/veilid-core/src/tests/common/test_veilid_config.rs index a10ece5f..514587b0 100644 --- a/veilid-core/src/tests/common/test_veilid_config.rs +++ b/veilid-core/src/tests/common/test_veilid_config.rs @@ -175,7 +175,7 @@ pub fn config_callback(key: String) -> ConfigCallbackReturn { match key.as_str() { "program_name" => Ok(Box::new(String::from("Veilid"))), "namespace" => Ok(Box::new(String::from(""))), - "log_to_api" => Ok(Box::new(false)), + "api_log_level" => Ok(Box::new(VeilidConfigLogLevel::Off)), "capabilities.protocol_udp" => Ok(Box::new(true)), "capabilities.protocol_connect_tcp" => Ok(Box::new(true)), "capabilities.protocol_accept_tcp" => Ok(Box::new(true)), @@ -276,7 +276,7 @@ pub async fn test_config() { let inner = vc.get(); assert_eq!(inner.program_name, String::from("Veilid")); assert_eq!(inner.namespace, String::from("")); - assert_eq!(inner.log_to_api, false); + assert_eq!(inner.api_log_level, VeilidConfigLogLevel::Off); assert_eq!(inner.capabilities.protocol_udp, true); assert_eq!(inner.capabilities.protocol_connect_tcp, true); assert_eq!(inner.capabilities.protocol_accept_tcp, true); diff --git a/veilid-core/src/veilid_api/mod.rs b/veilid-core/src/veilid_api/mod.rs index 4a25c165..33b10e30 100644 --- a/veilid-core/src/veilid_api/mod.rs +++ b/veilid-core/src/veilid_api/mod.rs @@ -5,6 +5,7 @@ pub use debug::*; pub use crate::rpc_processor::InfoAnswer; use crate::*; +use api_logger::*; use attachment_manager::*; use core::fmt; use network_manager::NetworkManager; @@ -1178,6 +1179,11 @@ impl VeilidAPI { Ok(()) } + // Change api logging level if it is enabled + pub async fn change_api_log_level(&self, log_level: VeilidConfigLogLevel) { + ApiLogger::change_log_level(log_level.to_level_filter()); + } + //////////////////////////////////////////////////////////////// // Direct Node Access (pretty much for testing only) diff --git a/veilid-core/src/veilid_config.rs b/veilid-core/src/veilid_config.rs index 0daf45cf..97a1edee 100644 --- a/veilid-core/src/veilid_config.rs +++ b/veilid-core/src/veilid_config.rs @@ -172,11 +172,39 @@ pub struct VeilidConfigCapabilities { pub protocol_accept_wss: bool, } +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum VeilidConfigLogLevel { + Off, + Error, + Warn, + Info, + Debug, + Trace, +} + +impl VeilidConfigLogLevel { + pub fn to_level_filter(&self) -> LevelFilter { + match self { + Self::Off => LevelFilter::Off, + Self::Error => LevelFilter::Error, + Self::Warn => LevelFilter::Warn, + Self::Info => LevelFilter::Info, + Self::Debug => LevelFilter::Debug, + Self::Trace => LevelFilter::Trace, + } + } +} +impl Default for VeilidConfigLogLevel { + fn default() -> Self { + Self::Off + } +} + #[derive(Default, Clone)] pub struct VeilidConfigInner { pub program_name: String, pub namespace: String, - pub log_to_api: bool, + pub api_log_level: VeilidConfigLogLevel, pub capabilities: VeilidConfigCapabilities, pub protected_store: VeilidConfigProtectedStore, pub table_store: VeilidConfigTableStore, @@ -221,7 +249,7 @@ impl VeilidConfig { let mut inner = self.inner.write(); get_config!(inner.program_name); get_config!(inner.namespace); - get_config!(inner.log_to_api); + get_config!(inner.api_log_level); get_config!(inner.capabilities.protocol_udp); get_config!(inner.capabilities.protocol_connect_tcp); get_config!(inner.capabilities.protocol_accept_tcp); diff --git a/veilid-core/src/veilid_core.rs b/veilid-core/src/veilid_core.rs index 8f137646..9486fbe3 100644 --- a/veilid-core/src/veilid_core.rs +++ b/veilid-core/src/veilid_core.rs @@ -1,3 +1,4 @@ +use crate::api_logger::*; use crate::attachment_manager::*; use crate::dht::crypto::Crypto; use crate::intf::*; @@ -86,6 +87,21 @@ impl VeilidCore { inner: &mut VeilidCoreInner, setup: VeilidCoreSetup, ) -> Result { + // Start up api logging early if it's in the config + let api_log_level: VeilidConfigLogLevel = + *(setup.config_callback)("api_log_level".to_owned())? + .downcast() + .map_err(|_| "incorrect type for key 'api_log_level'".to_owned())?; + if api_log_level != VeilidConfigLogLevel::Off { + ApiLogger::init( + api_log_level.to_level_filter(), + setup.update_callback.clone(), + ); + for ig in crate::DEFAULT_LOG_IGNORE_LIST { + ApiLogger::add_filter_ignore_str(ig); + } + } + trace!("VeilidCore::internal_startup starting"); cfg_if! { @@ -211,6 +227,8 @@ impl VeilidCore { } trace!("VeilidCore::shutdown complete"); + + ApiLogger::terminate(); } // stop the node gracefully because the veilid api was dropped diff --git a/veilid-flutter/lib/veilid.dart b/veilid-flutter/lib/veilid.dart index 60b1f937..f96f4b35 100644 --- a/veilid-flutter/lib/veilid.dart +++ b/veilid-flutter/lib/veilid.dart @@ -28,11 +28,4 @@ class Veilid { return veilidApi; } - // static const MethodChannel _channel = MethodChannel('veilid'); - - // static Future get platformVersion async { - // final String? version = await _channel.invokeMethod('getPlatformVersion'); - // return version; - // } - } diff --git a/veilid-flutter/rust/src/api.rs b/veilid-flutter/rust/src/api.rs index 365fcd7a..7bdb01dd 100644 --- a/veilid-flutter/rust/src/api.rs +++ b/veilid-flutter/rust/src/api.rs @@ -38,6 +38,7 @@ async fn take_veilid_api() -> Result { pub struct VeilidConfig { pub program_name: String, pub veilid_namespace: String, + pub api_log_level: VeilidLogLevel, // Capabilities pub capabilities__protocol_udp: bool, pub capabilities__protocol_connect_tcp: bool, @@ -125,6 +126,7 @@ impl VeilidConfig { let out: Box = match key { "program_name" => Box::new(self.program_name.clone()), "namespace" => Box::new(self.veilid_namespace.clone()), + "api_log_level" => Box::new(self.api_log_level.to_config_log_level()), "capabilities.protocol_udp" => Box::new(self.capabilities__protocol_udp.clone()), "capabilities.protocol_connect_tcp" => { Box::new(self.capabilities__protocol_connect_tcp.clone()) @@ -445,6 +447,16 @@ impl VeilidLogLevel { veilid_core::VeilidLogLevel::Trace => VeilidLogLevel::Trace, } } + + fn to_config_log_level(&self) -> VeilidConfigLogLevel { + match self { + Self::Error => VeilidConfigLogLevel::Error, + Self::Warn => VeilidConfigLogLevel::Warn, + Self::Info => VeilidConfigLogLevel::Info, + Self::Debug => VeilidConfigLogLevel::Debug, + Self::Trace => VeilidConfigLogLevel::Trace, + } + } } #[derive(Debug, Clone)] @@ -531,6 +543,16 @@ pub fn get_veilid_state() -> Result { // xxx api functions +pub fn change_api_log_level(level: VeilidLogLevel) -> Result<()> { + async_std::task::block_on(async { + let veilid_api = get_veilid_api().await?; + veilid_api + .change_api_log_level(log_level.to_config_log_level()) + .await; + Ok(()) + }) +} + pub fn shutdown_veilid_core() -> Result<()> { async_std::task::block_on(async { let veilid_api = get_veilid_api().await?; diff --git a/veilid-flutter/rust/src/bridge_generated.rs b/veilid-flutter/rust/src/bridge_generated.rs index 5d5007b3..c84f299f 100644 --- a/veilid-flutter/rust/src/bridge_generated.rs +++ b/veilid-flutter/rust/src/bridge_generated.rs @@ -40,6 +40,21 @@ pub extern "C" fn wire_get_veilid_state(port_: i64) { ) } +#[no_mangle] +pub extern "C" fn wire_change_api_log_level(port_: i64, level: i32) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap( + WrapInfo { + debug_name: "change_api_log_level", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || { + let api_level = level.wire2api(); + move |task_callback| change_api_log_level(api_level) + }, + ) +} + #[no_mangle] pub extern "C" fn wire_shutdown_veilid_core(port_: i64) { FLUTTER_RUST_BRIDGE_HANDLER.wrap( @@ -97,6 +112,7 @@ pub struct wire_uint_8_list { pub struct wire_VeilidConfig { program_name: *mut wire_uint_8_list, veilid_namespace: *mut wire_uint_8_list, + api_log_level: i32, capabilities__protocol_udp: bool, capabilities__protocol_connect_tcp: bool, capabilities__protocol_accept_tcp: bool, @@ -276,6 +292,7 @@ impl Wire2Api for wire_VeilidConfig { VeilidConfig { program_name: self.program_name.wire2api(), veilid_namespace: self.veilid_namespace.wire2api(), + api_log_level: self.api_log_level.wire2api(), capabilities__protocol_udp: self.capabilities__protocol_udp.wire2api(), capabilities__protocol_connect_tcp: self.capabilities__protocol_connect_tcp.wire2api(), capabilities__protocol_accept_tcp: self.capabilities__protocol_accept_tcp.wire2api(), @@ -388,6 +405,19 @@ impl Wire2Api for wire_VeilidConfig { } } +impl Wire2Api for i32 { + fn wire2api(self) -> VeilidLogLevel { + match self { + 0 => VeilidLogLevel::Error, + 1 => VeilidLogLevel::Warn, + 2 => VeilidLogLevel::Info, + 3 => VeilidLogLevel::Debug, + 4 => VeilidLogLevel::Trace, + _ => unreachable!("Invalid variant for VeilidLogLevel: {}", self), + } + } +} + // Section: impl NewWithNullPtr pub trait NewWithNullPtr { @@ -405,6 +435,7 @@ impl NewWithNullPtr for wire_VeilidConfig { Self { program_name: core::ptr::null_mut(), veilid_namespace: core::ptr::null_mut(), + api_log_level: Default::default(), capabilities__protocol_udp: Default::default(), capabilities__protocol_connect_tcp: Default::default(), capabilities__protocol_accept_tcp: Default::default(), diff --git a/veilid-server/src/settings.rs b/veilid-server/src/settings.rs index 9775ee6a..2f44eb8d 100644 --- a/veilid-server/src/settings.rs +++ b/veilid-server/src/settings.rs @@ -751,7 +751,7 @@ impl Settings { } else { format!("subnode{}", inner.testing.subnode_index) })), - "log_to_api" => Ok(Box::new(false)), + "api_log_level" => Ok(Box::new(veilid_core::VeilidConfigLogLevel::Off)), "capabilities.protocol_udp" => Ok(Box::new(true)), "capabilities.protocol_connect_tcp" => Ok(Box::new(true)), "capabilities.protocol_accept_tcp" => Ok(Box::new(true)), diff --git a/veilid-server/src/veilid_logs.rs b/veilid-server/src/veilid_logs.rs index 09503850..ec7beef5 100644 --- a/veilid-server/src/veilid_logs.rs +++ b/veilid-server/src/veilid_logs.rs @@ -18,14 +18,9 @@ impl VeilidLogs { let mut client_log_channel: Option = None; let mut client_log_channel_closer: Option = None; let mut cb = ConfigBuilder::new(); - cb.add_filter_ignore_str("async_std"); - cb.add_filter_ignore_str("async_io"); - cb.add_filter_ignore_str("polling"); - cb.add_filter_ignore_str("rustls"); - cb.add_filter_ignore_str("async_tungstenite"); - cb.add_filter_ignore_str("tungstenite"); - cb.add_filter_ignore_str("netlink_proto"); - cb.add_filter_ignore_str("netlink_sys"); + for ig in veilid_core::DEFAULT_LOG_IGNORE_LIST { + cb.add_filter_ignore_str(ig); + } if settingsr.logging.terminal.enabled { logs.push(TermLogger::new(