use crate::types::{LoggingLevel, OCIO_LOGGING_LEVEL_ENVVAR};
use std::cell::RefCell;
use std::sync::{Mutex, OnceLock};
struct Global {
level: LoggingLevel,
env_override: bool,
}
fn global() -> &'static Mutex<Global> {
static G: OnceLock<Mutex<Global>> = OnceLock::new();
G.get_or_init(|| {
let (level, env_override) = match std::env::var(OCIO_LOGGING_LEVEL_ENVVAR) {
Ok(s) if !s.is_empty() => {
let l = LoggingLevel::from_str_lossy(&s);
if l == LoggingLevel::Unknown {
eprintln!(
"[OpenColorIO Warning]: Invalid $OCIO_LOGGING_LEVEL specified. \
Options: none (0), warning (1), info (2), debug (3)"
);
(LoggingLevel::Info, true)
} else {
(l, true)
}
}
_ => (LoggingLevel::Info, false),
};
Mutex::new(Global {
level,
env_override,
})
})
}
thread_local! {
static CAPTURE: RefCell<Option<(LoggingLevel, String)>> = const { RefCell::new(None) };
}
pub fn logging_level() -> LoggingLevel {
let captured = CAPTURE.with(|c| c.borrow().as_ref().map(|(l, _)| *l));
if let Some(l) = captured {
return l;
}
global()
.lock()
.map(|g| g.level)
.unwrap_or(LoggingLevel::Info)
}
pub fn set_logging_level(level: LoggingLevel) {
if let Ok(mut g) = global().lock() {
if !g.env_override {
g.level = level;
}
}
}
fn log_message(prefix: &str, text: &str) {
let trimmed = super::utils::right_trim(text);
let parts = super::utils::split_by_lines(trimmed);
let captured = CAPTURE.with(|c| {
let mut c = c.borrow_mut();
if let Some((_, out)) = c.as_mut() {
for p in &parts {
out.push_str(prefix);
out.push_str(p);
out.push('\n');
}
true
} else {
false
}
});
if !captured {
for p in &parts {
eprintln!("{prefix}{p}");
}
}
}
pub fn log_error(text: &str) {
if logging_level() >= LoggingLevel::Warning && logging_level() != LoggingLevel::Unknown {
log_message("[OpenColorIO Error]: ", text);
}
}
pub fn log_warning(text: &str) {
if logging_level() >= LoggingLevel::Warning && logging_level() != LoggingLevel::Unknown {
log_message("[OpenColorIO Warning]: ", text);
}
}
pub fn log_info(text: &str) {
if logging_level() >= LoggingLevel::Info && logging_level() != LoggingLevel::Unknown {
log_message("[OpenColorIO Info]: ", text);
}
}
pub fn log_debug(text: &str) {
if logging_level() >= LoggingLevel::Debug && logging_level() != LoggingLevel::Unknown {
log_message("[OpenColorIO Debug]: ", text);
}
}
pub struct LogGuard {
previous: Option<(LoggingLevel, String)>,
}
impl LogGuard {
pub fn new() -> Self {
Self::with_level(LoggingLevel::Debug)
}
pub fn with_level(level: LoggingLevel) -> Self {
let previous = CAPTURE.with(|c| c.borrow_mut().replace((level, String::new())));
Self { previous }
}
pub fn output(&self) -> String {
CAPTURE.with(|c| {
c.borrow()
.as_ref()
.map(|(_, s)| s.clone())
.unwrap_or_default()
})
}
pub fn is_empty(&self) -> bool {
self.output().is_empty()
}
pub fn clear(&self) {
CAPTURE.with(|c| {
if let Some((_, s)) = c.borrow_mut().as_mut() {
s.clear();
}
});
}
pub fn find_and_remove(&self, line: &str) -> bool {
CAPTURE.with(|c| {
let mut c = c.borrow_mut();
if let Some((_, s)) = c.as_mut() {
let pat = format!("{}[\r\n]+", regex::escape(line));
if let Ok(re) = regex::Regex::new(&pat) {
if let Some(m) = re.find(s) {
let r = m.range();
s.replace_range(r, "");
return true;
}
}
}
false
})
}
pub fn find_all_and_remove(&self, pattern: &str) -> bool {
CAPTURE.with(|c| {
let mut c = c.borrow_mut();
let mut found = false;
if let Some((_, s)) = c.as_mut() {
if let Ok(re) = regex::Regex::new(pattern) {
while let Some(m) = re.find(s) {
found = true;
let r = m.range();
s.replace_range(r, "");
}
}
}
found
})
}
}
impl Default for LogGuard {
fn default() -> Self {
Self::new()
}
}
impl Drop for LogGuard {
fn drop(&mut self) {
let prev = self.previous.take();
CAPTURE.with(|c| *c.borrow_mut() = prev);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capture() {
let g = LogGuard::new();
log_warning("abc\ndef");
assert_eq!(
g.output(),
"[OpenColorIO Warning]: abc\n[OpenColorIO Warning]: def\n"
);
assert!(g.find_and_remove("[OpenColorIO Warning]: abc"));
assert_eq!(g.output(), "[OpenColorIO Warning]: def\n");
g.clear();
assert!(g.is_empty());
{
let g2 = LogGuard::with_level(LoggingLevel::None);
log_warning("x");
assert!(g2.is_empty());
}
log_info("y");
assert_eq!(g.output(), "[OpenColorIO Info]: y\n");
}
}