Complete first release candidate audit (#107)
Some checks failed
API and SemVer surface / api-surface (push) Failing after 1m34s
Native code generation / deterministic (push) Has been cancelled
Concurrency and resource soak audit / soak (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
performance evidence / audit (push) Has been cancelled
First release candidate / non-fuzz-release-gate (push) Has been cancelled
Release platform and feature matrix / audit (push) Has been cancelled
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Native release artifact audit / audit (push) Has been cancelled
Imaging and meshing gate / native (push) Failing after 53s
JPEG 2000 feature / linux (push) Successful in 2m49s
Native Rust workspace compile / compile (push) Failing after 59s
Skia feature / linux (push) Has been cancelled
Some checks failed
API and SemVer surface / api-surface (push) Failing after 1m34s
Native code generation / deterministic (push) Has been cancelled
Concurrency and resource soak audit / soak (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
performance evidence / audit (push) Has been cancelled
First release candidate / non-fuzz-release-gate (push) Has been cancelled
Release platform and feature matrix / audit (push) Has been cancelled
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Native release artifact audit / audit (push) Has been cancelled
Imaging and meshing gate / native (push) Failing after 53s
JPEG 2000 feature / linux (push) Successful in 2m49s
Native Rust workspace compile / compile (push) Failing after 59s
Skia feature / linux (push) Has been cancelled
This commit is contained in:
655
crates/libremetaverse/src/logger.rs
Normal file
655
crates/libremetaverse/src/logger.rs
Normal file
@@ -0,0 +1,655 @@
|
||||
//! Process-wide logging facade and callback compatibility layer.
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fmt;
|
||||
use std::sync::{
|
||||
Arc, Mutex, OnceLock,
|
||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
};
|
||||
use std::thread::ThreadId;
|
||||
use std::time::Duration;
|
||||
|
||||
use libremetaverse_types::compat::{
|
||||
CancellationToken, Close, ExternalError, Object, Subscription, Task, TaskCompletionSource,
|
||||
};
|
||||
|
||||
use crate::callback_runtime::AsyncInvocation;
|
||||
use crate::logging::{ILogger, ILoggerFactory, LogLevel};
|
||||
use crate::{Error, GridClient, Settings, Simulator};
|
||||
|
||||
struct LoggerState {
|
||||
factory: Option<Arc<dyn ILoggerFactory>>,
|
||||
logger: Option<Arc<dyn ILogger>>,
|
||||
callbacks: BTreeMap<u64, LoggerLogCallback>,
|
||||
next_callback: u64,
|
||||
}
|
||||
|
||||
impl Default for LoggerState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
factory: None,
|
||||
logger: None,
|
||||
callbacks: BTreeMap::new(),
|
||||
next_callback: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn state() -> &'static Mutex<LoggerState> {
|
||||
static STATE: OnceLock<Mutex<LoggerState>> = OnceLock::new();
|
||||
STATE.get_or_init(|| Mutex::new(LoggerState::default()))
|
||||
}
|
||||
|
||||
fn ensure_initialized() -> Arc<dyn ILogger> {
|
||||
let mut state = state()
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if let Some(logger) = state.logger.as_ref() {
|
||||
return Arc::clone(logger);
|
||||
}
|
||||
let factory: Arc<dyn ILoggerFactory> = Arc::new(ConsoleLoggerFactory::default());
|
||||
let logger = factory.create_logger("LibreMetaverse.Logger");
|
||||
state.factory = Some(factory);
|
||||
state.logger = Some(Arc::clone(&logger));
|
||||
logger
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ConsoleLoggerFactory {
|
||||
closed: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl ILoggerFactory for ConsoleLoggerFactory {
|
||||
fn create_logger(&self, name: &str) -> Arc<dyn ILogger> {
|
||||
Arc::new(ConsoleLogger {
|
||||
name: name.to_owned(),
|
||||
closed: Arc::clone(&self.closed),
|
||||
scopes: Arc::new(Mutex::new(HashMap::new())),
|
||||
next_scope: Arc::new(AtomicU64::new(1)),
|
||||
})
|
||||
}
|
||||
|
||||
fn shutdown(&self) -> Result<(), ExternalError> {
|
||||
self.closed.store(true, Ordering::Release);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
type ScopeMap = HashMap<ThreadId, Vec<(u64, Object)>>;
|
||||
|
||||
struct ConsoleLogger {
|
||||
name: String,
|
||||
closed: Arc<AtomicBool>,
|
||||
scopes: Arc<Mutex<ScopeMap>>,
|
||||
next_scope: Arc<AtomicU64>,
|
||||
}
|
||||
|
||||
impl ILogger for ConsoleLogger {
|
||||
fn is_enabled(&self, level: LogLevel) -> bool {
|
||||
!self.closed.load(Ordering::Acquire) && level != LogLevel::NONE && level >= LogLevel::DEBUG
|
||||
}
|
||||
|
||||
fn log(
|
||||
&self,
|
||||
level: LogLevel,
|
||||
message: &Object,
|
||||
exception: Option<&ExternalError>,
|
||||
client_name: Option<&str>,
|
||||
) {
|
||||
if !self.is_enabled(level) {
|
||||
return;
|
||||
}
|
||||
let scopes = self
|
||||
.scopes
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.get(&std::thread::current().id())
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let scope_text = scopes
|
||||
.iter()
|
||||
.map(|(_, state)| format!("{state:?}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
let level_name = match level {
|
||||
LogLevel::TRACE => "TRACE",
|
||||
LogLevel::DEBUG => "DEBUG",
|
||||
LogLevel::INFORMATION => "INFO",
|
||||
LogLevel::WARNING => "WARN",
|
||||
LogLevel::ERROR => "ERROR",
|
||||
LogLevel::CRITICAL => "CRITICAL",
|
||||
_ => "LOG",
|
||||
};
|
||||
let client = client_name
|
||||
.filter(|name| !name.is_empty())
|
||||
.map_or(String::new(), |name| format!(" [{name}]"));
|
||||
let scope = if scope_text.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" [{scope_text}]")
|
||||
};
|
||||
let exception = exception.map_or(String::new(), |error| format!(": {}", error.0));
|
||||
eprintln!(
|
||||
"[{level_name}] [{}]{client}{scope} {message:?}{exception}",
|
||||
self.name
|
||||
);
|
||||
}
|
||||
|
||||
fn begin_scope(&self, state: Object) -> Box<dyn Close> {
|
||||
let thread_id = std::thread::current().id();
|
||||
let id = self.next_scope.fetch_add(1, Ordering::Relaxed);
|
||||
self.scopes
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.entry(thread_id)
|
||||
.or_default()
|
||||
.push((id, state));
|
||||
Box::new(ConsoleScope {
|
||||
scopes: Arc::clone(&self.scopes),
|
||||
thread_id,
|
||||
id: Some(id),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct ConsoleScope {
|
||||
scopes: Arc<Mutex<ScopeMap>>,
|
||||
thread_id: ThreadId,
|
||||
id: Option<u64>,
|
||||
}
|
||||
|
||||
impl Close for ConsoleScope {
|
||||
fn close(&mut self) -> Result<(), ExternalError> {
|
||||
let Some(id) = self.id.take() else {
|
||||
return Ok(());
|
||||
};
|
||||
let mut scopes = self
|
||||
.scopes
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if let Some(thread_scopes) = scopes.get_mut(&self.thread_id) {
|
||||
thread_scopes.retain(|(scope_id, _)| *scope_id != id);
|
||||
if thread_scopes.is_empty() {
|
||||
scopes.remove(&self.thread_id);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ConsoleScope {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.close();
|
||||
}
|
||||
}
|
||||
|
||||
struct NoopScope;
|
||||
|
||||
impl Close for NoopScope {
|
||||
fn close(&mut self) -> Result<(), ExternalError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
type LogHandler = Arc<dyn Fn(Object, LogLevel) + Send + Sync>;
|
||||
|
||||
pub struct LoggerCallbackTarget(pub LogHandler);
|
||||
|
||||
impl fmt::Debug for LoggerCallbackTarget {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("LoggerCallbackTarget(..)")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LoggerLogCallback(LogHandler);
|
||||
|
||||
impl LoggerLogCallback {
|
||||
pub fn from_callback(callback: impl Fn(Object, LogLevel) + Send + Sync + 'static) -> Self {
|
||||
Self(Arc::new(callback))
|
||||
}
|
||||
|
||||
pub fn new(object: Object, method: isize) -> Result<Self, Error> {
|
||||
if method != 0 {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
object
|
||||
.downcast_arc::<LoggerCallbackTarget>()
|
||||
.map(|target| Self(Arc::clone(&target.0)))
|
||||
.ok_or(Error::Argument)
|
||||
}
|
||||
|
||||
pub fn begin_invoke(
|
||||
&self,
|
||||
message: Object,
|
||||
level: LogLevel,
|
||||
callback: Box<dyn Fn(&dyn Any) + Send + Sync>,
|
||||
object: Object,
|
||||
) -> Result<Box<dyn Any + Send + Sync>, Error> {
|
||||
let invocation = AsyncInvocation::pending(object);
|
||||
let worker_invocation = invocation.clone();
|
||||
let handler = Arc::clone(&self.0);
|
||||
std::thread::Builder::new()
|
||||
.name("logger-callback".to_owned())
|
||||
.spawn(move || {
|
||||
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
handler(message, level);
|
||||
}))
|
||||
.map_err(|_| Error::InvalidOperation);
|
||||
worker_invocation.complete(result);
|
||||
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
callback(&worker_invocation);
|
||||
}));
|
||||
})
|
||||
.map_err(|_| Error::InvalidOperation)?;
|
||||
Ok(Box::new(invocation))
|
||||
}
|
||||
|
||||
pub fn end_invoke(&self, result: Box<dyn Any + Send + Sync>) -> Result<(), Error> {
|
||||
result
|
||||
.downcast::<AsyncInvocation>()
|
||||
.map_err(|_| Error::Argument)?
|
||||
.wait()
|
||||
}
|
||||
|
||||
pub fn invoke(&self, message: Object, level: LogLevel) -> Result<(), Error> {
|
||||
(self.0)(message, level);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Logger;
|
||||
|
||||
impl Logger {
|
||||
pub fn subscribe_on_log_message(handler: Option<LoggerLogCallback>) -> Subscription {
|
||||
let Some(handler) = handler else {
|
||||
return Subscription::detached();
|
||||
};
|
||||
let id = {
|
||||
let mut state = state()
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let id = state.next_callback;
|
||||
state.next_callback = state.next_callback.wrapping_add(1).max(1);
|
||||
state.callbacks.insert(id, handler);
|
||||
id
|
||||
};
|
||||
Subscription::new(move || {
|
||||
state()
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.callbacks
|
||||
.remove(&id);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn begin_scope(state_value: Object) -> Result<Box<dyn Close>, Error> {
|
||||
let logger = state()
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.logger
|
||||
.clone();
|
||||
Ok(logger.map_or_else(
|
||||
|| Box::new(NoopScope) as Box<dyn Close>,
|
||||
|logger| logger.begin_scope(state_value),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn begin_client_scope(mut client: GridClient) -> Result<Box<dyn Close>, Error> {
|
||||
let log_names = client.settings().logging.log_names;
|
||||
if !log_names {
|
||||
return Ok(Box::new(NoopScope));
|
||||
}
|
||||
let name = client.self_().name();
|
||||
if name.is_empty() {
|
||||
return Ok(Box::new(NoopScope));
|
||||
}
|
||||
Self::begin_scope(Object::Map(HashMap::from([(
|
||||
"Client".to_owned(),
|
||||
Object::String(name),
|
||||
)])))
|
||||
}
|
||||
|
||||
pub fn begin_region_scope(simulator: Simulator) -> Result<Box<dyn Close>, Error> {
|
||||
let name = if simulator.name.is_empty() {
|
||||
simulator.handle.to_string()
|
||||
} else {
|
||||
simulator.name.clone()
|
||||
};
|
||||
Self::begin_scope(Object::Map(HashMap::from([(
|
||||
"Region".to_owned(),
|
||||
Object::String(name),
|
||||
)])))
|
||||
}
|
||||
|
||||
pub fn create_default_console_logger_factory() -> Result<Box<dyn ILoggerFactory>, Error> {
|
||||
Ok(Box::new(ConsoleLoggerFactory::default()))
|
||||
}
|
||||
|
||||
pub fn set_logger_factory(factory: Box<dyn ILoggerFactory>, name: String) -> Result<(), Error> {
|
||||
let factory: Arc<dyn ILoggerFactory> = Arc::from(factory);
|
||||
let logger = factory.create_logger(&name);
|
||||
let previous = {
|
||||
let mut state = state()
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let previous = state.factory.replace(Arc::clone(&factory));
|
||||
state.logger = Some(logger);
|
||||
previous
|
||||
};
|
||||
if let Some(previous) = previous {
|
||||
let _ = previous.shutdown();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn shutdown_with_method() -> Result<(), Error> {
|
||||
let factory = {
|
||||
let mut state = state()
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
state.logger = None;
|
||||
state.factory.take()
|
||||
};
|
||||
if let Some(factory) = factory {
|
||||
let _ = factory.shutdown();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn shutdown_with_nullable_cancellation_token(
|
||||
timeout: Option<Option<Duration>>,
|
||||
cancellation_token: Option<CancellationToken>,
|
||||
) -> Result<bool, Error> {
|
||||
let factory = {
|
||||
let mut state = state()
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
state.logger = None;
|
||||
state.factory.take()
|
||||
};
|
||||
let Some(factory) = factory else {
|
||||
return Ok(true);
|
||||
};
|
||||
let completion = TaskCompletionSource::new();
|
||||
let shutdown_completion = completion.clone();
|
||||
if std::thread::Builder::new()
|
||||
.name("logger-shutdown".to_owned())
|
||||
.spawn(move || {
|
||||
let _ = shutdown_completion.try_set_result(factory.shutdown().is_ok());
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
let cancellation = cancellation_token.map(|token| {
|
||||
let completion = completion.clone();
|
||||
token.register_callback(Arc::new(move || {
|
||||
let _ = completion.try_set_result(false);
|
||||
}))
|
||||
});
|
||||
if let Some(timeout) = timeout.flatten() {
|
||||
let timeout_completion = completion.clone();
|
||||
let _ = std::thread::Builder::new()
|
||||
.name("logger-shutdown-timeout".to_owned())
|
||||
.spawn(move || {
|
||||
std::thread::sleep(timeout);
|
||||
let _ = timeout_completion.try_set_result(false);
|
||||
});
|
||||
}
|
||||
let result = completion.future().await;
|
||||
drop(cancellation);
|
||||
result
|
||||
}
|
||||
|
||||
fn callbacks() -> Vec<LoggerLogCallback> {
|
||||
state()
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.callbacks
|
||||
.values()
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn raise(message: &Object, level: LogLevel) {
|
||||
for callback in Self::callbacks() {
|
||||
let message = message.clone();
|
||||
let _ = std::thread::Builder::new()
|
||||
.name("logger-event".to_owned())
|
||||
.spawn(move || {
|
||||
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
let _ = callback.invoke(message, level);
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn client_name(mut client: Option<GridClient>) -> Option<String> {
|
||||
let client = client.as_mut()?;
|
||||
if !client.settings().logging.log_names {
|
||||
return None;
|
||||
}
|
||||
let name = client.self_().name();
|
||||
(!name.is_empty()).then_some(name)
|
||||
}
|
||||
|
||||
fn log_to_sink(
|
||||
message: &Object,
|
||||
level: LogLevel,
|
||||
exception: Option<&ExternalError>,
|
||||
client_name: Option<&str>,
|
||||
) {
|
||||
let logger = ensure_initialized();
|
||||
if logger.is_enabled(level) {
|
||||
logger.log(level, message, exception, client_name);
|
||||
}
|
||||
}
|
||||
|
||||
fn log(
|
||||
message: Object,
|
||||
level: LogLevel,
|
||||
client: Option<GridClient>,
|
||||
exception: Option<ExternalError>,
|
||||
) -> Result<(), Error> {
|
||||
Self::raise(&message, level);
|
||||
let configured = Settings::log_level();
|
||||
if configured == LogLevel::NONE || configured > level {
|
||||
return Ok(());
|
||||
}
|
||||
let client_name = Self::client_name(client);
|
||||
Self::log_to_sink(&message, level, exception.as_ref(), client_name.as_deref());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn log_named(
|
||||
message: Object,
|
||||
level: LogLevel,
|
||||
exception: Option<ExternalError>,
|
||||
client_name: String,
|
||||
) -> Result<(), Error> {
|
||||
Self::log_to_sink(&message, level, exception.as_ref(), Some(&client_name));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_enabled(level: LogLevel) -> Result<bool, Error> {
|
||||
Ok(ensure_initialized().is_enabled(level))
|
||||
}
|
||||
|
||||
pub fn log_with_object_log_level_grid_client(
|
||||
message: Object,
|
||||
level: LogLevel,
|
||||
client: Option<GridClient>,
|
||||
) -> Result<(), Error> {
|
||||
Self::log(message, level, client, None)
|
||||
}
|
||||
|
||||
pub fn log_with_object_log_level_grid_client_exception(
|
||||
message: Object,
|
||||
level: LogLevel,
|
||||
client: Option<GridClient>,
|
||||
exception: Option<ExternalError>,
|
||||
) -> Result<(), Error> {
|
||||
Self::log(message, level, client, exception)
|
||||
}
|
||||
|
||||
pub fn log_with_object_log_level_exception(
|
||||
message: Object,
|
||||
level: LogLevel,
|
||||
exception: ExternalError,
|
||||
) -> Result<(), Error> {
|
||||
Self::log(message, level, None, Some(exception))
|
||||
}
|
||||
|
||||
pub fn debug_log(message: Object, client: Option<GridClient>) -> Result<(), Error> {
|
||||
let level = Settings::log_level();
|
||||
if level > LogLevel::DEBUG {
|
||||
return Ok(());
|
||||
}
|
||||
Self::log(message, level, client, None)
|
||||
}
|
||||
|
||||
pub async fn use_scope(
|
||||
state: Object,
|
||||
action: Box<dyn Fn() -> Task<()> + Send + Sync>,
|
||||
) -> Result<(), Error> {
|
||||
let mut scope = Self::begin_scope(state)?;
|
||||
let result = action().await;
|
||||
let close = scope.close().map_err(|_| Error::InvalidOperation);
|
||||
result.and(close)
|
||||
}
|
||||
|
||||
pub async fn use_client_scope(
|
||||
client: GridClient,
|
||||
action: Box<dyn Fn() -> Task<()> + Send + Sync>,
|
||||
) -> Result<(), Error> {
|
||||
let mut scope = Self::begin_client_scope(client)?;
|
||||
let result = action().await;
|
||||
let close = scope.close().map_err(|_| Error::InvalidOperation);
|
||||
result.and(close)
|
||||
}
|
||||
|
||||
pub async fn use_region_scope(
|
||||
simulator: Simulator,
|
||||
action: Box<dyn Fn() -> Task<()> + Send + Sync>,
|
||||
) -> Result<(), Error> {
|
||||
let mut scope = Self::begin_region_scope(simulator)?;
|
||||
let result = action().await;
|
||||
let close = scope.close().map_err(|_| Error::InvalidOperation);
|
||||
result.and(close)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! level_methods {
|
||||
($client:ident, $client_error:ident, $named:ident, $named_error:ident, $level:expr) => {
|
||||
impl Logger {
|
||||
pub fn $client(message: Object, client: Option<GridClient>) -> Result<(), Error> {
|
||||
Self::log(message, $level, client, None)
|
||||
}
|
||||
|
||||
pub fn $client_error(
|
||||
message: Object,
|
||||
exception: ExternalError,
|
||||
client: Option<GridClient>,
|
||||
) -> Result<(), Error> {
|
||||
Self::log(message, $level, client, Some(exception))
|
||||
}
|
||||
|
||||
pub fn $named(message: Object, client_name: String) -> Result<(), Error> {
|
||||
Self::log_named(message, $level, None, client_name)
|
||||
}
|
||||
|
||||
pub fn $named_error(
|
||||
message: Object,
|
||||
exception: ExternalError,
|
||||
client_name: String,
|
||||
) -> Result<(), Error> {
|
||||
Self::log_named(message, $level, Some(exception), client_name)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
level_methods!(
|
||||
trace_with_object_grid_client,
|
||||
trace_with_object_exception_grid_client,
|
||||
trace_with_object_string,
|
||||
trace_with_object_exception_string,
|
||||
LogLevel::TRACE
|
||||
);
|
||||
level_methods!(
|
||||
debug_with_object_grid_client,
|
||||
debug_with_object_exception_grid_client,
|
||||
debug_with_object_string,
|
||||
debug_with_object_exception_string,
|
||||
LogLevel::DEBUG
|
||||
);
|
||||
level_methods!(
|
||||
info_with_object_grid_client,
|
||||
info_with_object_exception_grid_client,
|
||||
info_with_object_string,
|
||||
info_with_object_exception_string,
|
||||
LogLevel::INFORMATION
|
||||
);
|
||||
level_methods!(
|
||||
warn_with_object_grid_client,
|
||||
warn_with_object_exception_grid_client,
|
||||
warn_with_object_string,
|
||||
warn_with_object_exception_string,
|
||||
LogLevel::WARNING
|
||||
);
|
||||
level_methods!(
|
||||
error_with_object_grid_client,
|
||||
error_with_object_exception_grid_client,
|
||||
error_with_object_string,
|
||||
error_with_object_exception_string,
|
||||
LogLevel::ERROR
|
||||
);
|
||||
level_methods!(
|
||||
critical_with_object_grid_client,
|
||||
critical_with_object_exception_grid_client,
|
||||
critical_with_object_string,
|
||||
critical_with_object_exception_string,
|
||||
LogLevel::CRITICAL
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
|
||||
#[test]
|
||||
fn event_subscription_delivers_and_unregisters() {
|
||||
Logger::shutdown_with_method().unwrap();
|
||||
let calls = Arc::new(AtomicUsize::new(0));
|
||||
let observed = Arc::clone(&calls);
|
||||
let subscription = Logger::subscribe_on_log_message(Some(
|
||||
LoggerLogCallback::from_callback(move |_, _| {
|
||||
observed.fetch_add(1, Ordering::Relaxed);
|
||||
}),
|
||||
));
|
||||
Logger::info_with_object_grid_client(Object::String("one".to_owned()), None).unwrap();
|
||||
for _ in 0..100 {
|
||||
if calls.load(Ordering::Relaxed) == 1 {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
assert_eq!(calls.load(Ordering::Relaxed), 1);
|
||||
drop(subscription);
|
||||
Logger::info_with_object_grid_client(Object::String("two".to_owned()), None).unwrap();
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
assert_eq!(calls.load(Ordering::Relaxed), 1);
|
||||
Logger::shutdown_with_method().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope_close_is_idempotent() {
|
||||
let _ = ensure_initialized();
|
||||
let mut scope = Logger::begin_scope(Object::String("test".to_owned())).unwrap();
|
||||
scope.close().unwrap();
|
||||
scope.close().unwrap();
|
||||
Logger::shutdown_with_method().unwrap();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user