use super::GlobalConfig;
use crate::config::{
autotune::AutotuneLogLevel, compilation::CompilationLogLevel, memory::MemoryLogLevel,
profiling::ProfilingLogLevel, streaming::StreamingLogLevel,
};
use alloc::{string::ToString, sync::Arc, vec::Vec};
use core::fmt::Display;
use hashbrown::HashMap;
#[cfg(std_io)]
use std::{
fs::{File, OpenOptions},
io::{BufWriter, Write},
path::PathBuf,
};
#[cfg(feature = "std")]
use std::{eprintln, println};
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(bound = "")]
pub struct LoggerConfig<L: LogLevel> {
#[serde(default)]
#[cfg(std_io)]
pub file: Option<PathBuf>,
#[serde(default = "append_default")]
pub append: bool,
#[serde(default)]
pub stdout: bool,
#[serde(default)]
pub stderr: bool,
#[serde(default)]
pub log: Option<LogCrateLevel>,
#[serde(default)]
pub level: L,
}
impl<L: LogLevel> Default for LoggerConfig<L> {
fn default() -> Self {
Self {
#[cfg(std_io)]
file: None,
append: true,
#[cfg(feature = "autotune-checks")]
stdout: true,
#[cfg(not(feature = "autotune-checks"))]
stdout: false,
stderr: false,
log: None,
level: L::default(),
}
}
}
#[derive(
Clone, Copy, Debug, Default, serde::Serialize, serde::Deserialize, Hash, PartialEq, Eq,
)]
pub enum LogCrateLevel {
#[default]
#[serde(rename = "info")]
Info,
#[serde(rename = "debug")]
Debug,
#[serde(rename = "trace")]
Trace,
}
impl LogLevel for u32 {}
fn append_default() -> bool {
true
}
pub trait LogLevel:
serde::de::DeserializeOwned + serde::Serialize + Clone + Copy + core::fmt::Debug + Default
{
}
#[derive(Debug)]
pub struct Logger {
loggers: Vec<LoggerKind>,
compilation_index: Vec<usize>,
profiling_index: Vec<usize>,
autotune_index: Vec<usize>,
streaming_index: Vec<usize>,
memory_index: Vec<usize>,
pub config: Arc<GlobalConfig>,
}
impl Default for Logger {
fn default() -> Self {
Self::new()
}
}
impl Logger {
pub fn new() -> Self {
let config = GlobalConfig::get();
let mut loggers = Vec::new();
let mut compilation_index = Vec::new();
let mut profiling_index = Vec::new();
let mut autotune_index = Vec::new();
let mut streaming_index = Vec::new();
let mut memory_index = Vec::new();
#[derive(Hash, PartialEq, Eq)]
enum LoggerId {
#[cfg(std_io)]
File(PathBuf),
#[cfg(feature = "std")]
Stdout,
#[cfg(feature = "std")]
Stderr,
LogCrate(LogCrateLevel),
}
let mut logger2index = HashMap::<LoggerId, usize>::new();
fn new_logger<S: Clone, ID: Fn(S) -> LoggerId, LG: Fn(S) -> LoggerKind>(
setting_index: &mut Vec<usize>,
loggers: &mut Vec<LoggerKind>,
logger2index: &mut HashMap<LoggerId, usize>,
state: S,
func_id: ID,
func_logger: LG,
) {
let id = func_id(state.clone());
if let Some(index) = logger2index.get(&id) {
setting_index.push(*index);
} else {
let logger = func_logger(state);
let index = loggers.len();
logger2index.insert(id, index);
loggers.push(logger);
setting_index.push(index);
}
}
fn register_logger<L: LogLevel>(
#[allow(unused_variables)] kind: &LoggerConfig<L>, #[allow(unused_variables)] append: bool, level: Option<LogCrateLevel>,
setting_index: &mut Vec<usize>,
loggers: &mut Vec<LoggerKind>,
logger2index: &mut HashMap<LoggerId, usize>,
) {
#[cfg(std_io)]
if let Some(file) = &kind.file {
new_logger(
setting_index,
loggers,
logger2index,
(file, append),
|(file, _append)| LoggerId::File(file.clone()),
|(file, append)| LoggerKind::File(FileLogger::new(file, append)),
);
}
#[cfg(feature = "std")]
if kind.stdout {
new_logger(
setting_index,
loggers,
logger2index,
(),
|_| LoggerId::Stdout,
|_| LoggerKind::Stdout,
);
}
#[cfg(feature = "std")]
if kind.stderr {
new_logger(
setting_index,
loggers,
logger2index,
(),
|_| LoggerId::Stderr,
|_| LoggerKind::Stderr,
);
}
if let Some(level) = level {
new_logger(
setting_index,
loggers,
logger2index,
level,
LoggerId::LogCrate,
LoggerKind::Log,
);
}
}
if let CompilationLogLevel::Disabled = config.compilation.logger.level {
} else {
register_logger(
&config.compilation.logger,
config.compilation.logger.append,
config.compilation.logger.log,
&mut compilation_index,
&mut loggers,
&mut logger2index,
)
}
if let ProfilingLogLevel::Disabled = config.profiling.logger.level {
} else {
register_logger(
&config.profiling.logger,
config.profiling.logger.append,
config.profiling.logger.log,
&mut profiling_index,
&mut loggers,
&mut logger2index,
)
}
if let AutotuneLogLevel::Disabled = config.autotune.logger.level {
} else {
register_logger(
&config.autotune.logger,
config.autotune.logger.append,
config.autotune.logger.log,
&mut autotune_index,
&mut loggers,
&mut logger2index,
)
}
if let StreamingLogLevel::Disabled = config.streaming.logger.level {
} else {
register_logger(
&config.streaming.logger,
config.streaming.logger.append,
config.streaming.logger.log,
&mut streaming_index,
&mut loggers,
&mut logger2index,
)
}
if let MemoryLogLevel::Disabled = config.memory.logger.level {
} else {
register_logger(
&config.memory.logger,
config.memory.logger.append,
config.memory.logger.log,
&mut memory_index,
&mut loggers,
&mut logger2index,
)
}
Self {
loggers,
compilation_index,
profiling_index,
autotune_index,
streaming_index,
memory_index,
config,
}
}
pub fn log_streaming<S: Display>(&mut self, msg: &S) {
let length = self.streaming_index.len();
if length > 1 {
let msg = msg.to_string();
for i in 0..length {
let index = self.streaming_index[i];
self.log(&msg, index)
}
} else if let Some(index) = self.streaming_index.first() {
self.log(&msg, *index)
}
}
pub fn log_memory<S: Display>(&mut self, msg: &S) {
let length = self.memory_index.len();
if length > 1 {
let msg = msg.to_string();
for i in 0..length {
let index = self.memory_index[i];
self.log(&msg, index)
}
} else if let Some(index) = self.memory_index.first() {
self.log(&msg, *index)
}
}
pub fn log_compilation<S: Display>(&mut self, msg: &S) {
let length = self.compilation_index.len();
if length > 1 {
let msg = msg.to_string();
for i in 0..length {
let index = self.compilation_index[i];
self.log(&msg, index)
}
} else if let Some(index) = self.compilation_index.first() {
self.log(&msg, *index)
}
}
pub fn log_profiling<S: Display>(&mut self, msg: &S) {
let length = self.profiling_index.len();
if length > 1 {
let msg = msg.to_string();
for i in 0..length {
let index = self.profiling_index[i];
self.log(&msg, index)
}
} else if let Some(index) = self.profiling_index.first() {
self.log(&msg, *index)
}
}
pub fn log_autotune<S: Display>(&mut self, msg: &S) {
let length = self.autotune_index.len();
if length > 1 {
let msg = msg.to_string();
for i in 0..length {
let index = self.autotune_index[i];
self.log(&msg, index)
}
} else if let Some(index) = self.autotune_index.first() {
self.log(&msg, *index)
}
}
pub fn log_level_streaming(&self) -> StreamingLogLevel {
self.config.streaming.logger.level
}
pub fn log_level_autotune(&self) -> AutotuneLogLevel {
self.config.autotune.logger.level
}
pub fn log_level_compilation(&self) -> CompilationLogLevel {
self.config.compilation.logger.level
}
pub fn log_level_profiling(&self) -> ProfilingLogLevel {
self.config.profiling.logger.level
}
fn log<S: Display>(&mut self, msg: &S, index: usize) {
let logger = &mut self.loggers[index];
logger.log(msg);
}
}
#[derive(Debug)]
enum LoggerKind {
#[cfg(std_io)]
File(FileLogger),
#[cfg(feature = "std")]
Stdout,
#[cfg(feature = "std")]
Stderr,
Log(LogCrateLevel),
}
impl LoggerKind {
fn log<S: Display>(&mut self, msg: &S) {
match self {
#[cfg(std_io)]
LoggerKind::File(file_logger) => file_logger.log(msg),
#[cfg(feature = "std")]
LoggerKind::Stdout => println!("{msg}"),
#[cfg(feature = "std")]
LoggerKind::Stderr => eprintln!("{msg}"),
LoggerKind::Log(level) => match level {
LogCrateLevel::Info => log::info!("{msg}"),
LogCrateLevel::Trace => log::debug!("{msg}"),
LogCrateLevel::Debug => log::trace!("{msg}"),
},
}
}
}
#[derive(Debug)]
#[cfg(std_io)]
struct FileLogger {
writer: BufWriter<File>,
}
#[cfg(std_io)]
impl FileLogger {
fn new(path: &PathBuf, append: bool) -> Self {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).unwrap();
}
let file = OpenOptions::new()
.write(true)
.append(append)
.create(true)
.open(path)
.unwrap();
Self {
writer: BufWriter::new(file),
}
}
fn log<S: Display>(&mut self, msg: &S) {
writeln!(self.writer, "{msg}").expect("Should be able to log debug information.");
self.writer.flush().expect("Can complete write operation.");
}
}