use std::borrow::Cow;
use std::cell::RefCell;
use crate::types::LogError;
use crate::{LogSettings, LoggerConfigHandle, init_log};
use candid::{CandidType, Decode, Encode};
pub use ic_mple_utils::store::Storage;
use ic_stable_structures::DefaultMemoryImpl;
use ic_stable_structures::memory_manager::VirtualMemory;
use ic_stable_structures::storable::Bound;
use ic_stable_structures::{StableCell, Storable};
use serde::Deserialize;
impl Storable for LogSettings {
const BOUND: Bound = Bound::Unbounded;
fn to_bytes(&self) -> std::borrow::Cow<'_, [u8]> {
Cow::from(Encode!(&self).unwrap())
}
fn from_bytes(bytes: Cow<[u8]>) -> Self {
Decode!(&bytes, Self).unwrap()
}
fn into_bytes(self) -> Vec<u8> {
Encode!(&self).unwrap()
}
}
const DEFAULT_IN_MEMORY_RECORDS: usize = 1024;
const DEFAULT_MAX_RECORD_LENGTH: usize = 1024;
#[derive(Default, Debug, Clone, CandidType, Deserialize, PartialEq, Eq)]
pub struct LogServiceSettings {
pub enable_console: Option<bool>,
pub in_memory_records: Option<usize>,
pub max_record_length: Option<usize>,
pub log_filter: Option<String>,
}
impl From<LogServiceSettings> for LogSettings {
fn from(settings: LogServiceSettings) -> Self {
Self {
enable_console: settings.enable_console.unwrap_or(false),
in_memory_records: settings
.in_memory_records
.unwrap_or(DEFAULT_IN_MEMORY_RECORDS),
max_record_length: settings
.max_record_length
.unwrap_or(DEFAULT_MAX_RECORD_LENGTH),
log_filter: settings.log_filter.unwrap_or("warn".to_string()),
}
}
}
pub type LoggerServiceStorage = StableCell<LogSettings, VirtualMemory<DefaultMemoryImpl>>;
thread_local! {
static LOGGER_CONFIG: RefCell<Option<LoggerConfigHandle>> = const { RefCell::new(None) };
}
pub struct LoggerConfigService<S: Storage<LoggerServiceStorage>> {
pub log_settings_store: S,
}
impl<S: Storage<LoggerServiceStorage>> LoggerConfigService<S> {
pub fn new(log_settings_store: S) -> Self {
Self { log_settings_store }
}
pub fn init(&mut self, log_settings: Option<LogServiceSettings>) -> Result<(), LogError> {
if LOGGER_CONFIG.with_borrow(|logger_config| logger_config.is_some()) {
return Err(LogError::AlreadyInitialized);
}
if let Some(log_settings) = log_settings {
self.log_settings_store.with_borrow_mut(|store| {
store.set(log_settings.into());
});
}
self.log_settings_store.with_borrow(|store| {
LOGGER_CONFIG.with_borrow_mut(|logger_config| {
*logger_config = Some(init_log(store.get())?);
Ok(())
})
})
}
pub fn set_logger_filter(&mut self, filter: &str) -> Result<(), LogError> {
self.update_log_settings(filter)?;
LOGGER_CONFIG.with_borrow_mut(|logger_config| match logger_config.as_mut() {
Some(logger_config) => logger_config.update_filters(filter),
None => Err(LogError::NotInitialized),
})
}
pub fn get_logger_filter(&self) -> String {
self.log_settings_store
.with_borrow(|store| store.get().log_filter.clone())
}
fn update_log_settings(&mut self, filter: &str) -> Result<(), LogError> {
self.log_settings_store.with_borrow_mut(|store| {
let mut log_settings = store.get().clone();
log_settings.log_filter = filter.to_string();
store.set(log_settings);
});
Ok(())
}
}
#[cfg(test)]
mod test {
use std::cell::RefCell;
use ic_stable_structures::{
DefaultMemoryImpl,
memory_manager::{MemoryId, MemoryManager},
};
use super::*;
thread_local! {
static LOG_SETTINGS_STORE: RefCell<LoggerServiceStorage> = RefCell::new(
StableCell::new(MemoryManager::init(DefaultMemoryImpl::default()).get(MemoryId::new(1)), LogSettings::default())
);
}
#[test]
fn test_logger_config_service_with_thread_local() {
let logger_config_service = LoggerConfigService::new(&LOG_SETTINGS_STORE);
assert_eq!(logger_config_service.get_logger_filter(), "warn");
}
#[test]
fn test_logger_config_service_with_local_var() {
let store = RefCell::new(StableCell::new(
MemoryManager::init(DefaultMemoryImpl::default()).get(MemoryId::new(1)),
LogSettings::default(),
));
let logger_config_service = LoggerConfigService::new(store);
assert_eq!(logger_config_service.get_logger_filter(), "warn");
}
}