#![forbid(unsafe_code)]
#![forbid(missing_docs)]
extern crate self as canlog;
#[cfg(test)]
mod tests;
mod types;
pub use crate::types::{LogFilter, RegexString, RegexSubstitution, Sort};
pub use ic_canister_log::{
declare_log_buffer, export as export_logs, log as raw_log, GlobalBuffer, Sink,
};
use serde::{Deserialize, Serialize};
#[cfg(any(feature = "derive", test))]
#[doc(inline)]
pub use canlog_derive::LogPriorityLevels;
#[macro_export]
macro_rules! log {
($enum_variant:expr, $($args:tt)*) => {
{
use ::canlog::LogPriorityLevels;
::canlog::raw_log!($enum_variant.get_sink(), $($args)*);
}
};
}
pub trait LogPriorityLevels {
#[doc(hidden)]
fn get_buffer(&self) -> &'static GlobalBuffer;
#[doc(hidden)]
fn get_sink(&self) -> &impl Sink;
fn display_name(&self) -> &'static str;
fn get_priorities() -> &'static [Self]
where
Self: Sized;
}
pub trait GetLogFilter {
fn get_log_filter() -> LogFilter;
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, serde::Serialize)]
pub struct LogEntry<Priority> {
pub timestamp: u64,
pub priority: Priority,
pub file: String,
pub line: u32,
pub message: String,
pub counter: u64,
}
#[derive(Clone, Debug, Deserialize, serde::Serialize)]
pub struct Log<Priority> {
pub entries: Vec<LogEntry<Priority>>,
}
impl<Priority> Default for Log<Priority> {
fn default() -> Self {
Self { entries: vec![] }
}
}
impl<'de, Priority> Log<Priority>
where
Priority: LogPriorityLevels + Clone + Copy + Deserialize<'de> + Serialize + 'static,
{
pub fn push_logs(&mut self, priority: Priority) {
for entry in export_logs(priority.get_buffer()) {
self.entries.push(LogEntry {
timestamp: entry.timestamp,
counter: entry.counter,
priority,
file: entry.file.to_string(),
line: entry.line,
message: entry.message,
});
}
}
pub fn push_all(&mut self) {
Priority::get_priorities()
.iter()
.for_each(|priority| self.push_logs(*priority));
}
pub fn serialize_logs(&self, max_body_size: usize) -> String {
let mut entries_json: String = serde_json::to_string(&self).unwrap_or_default();
if entries_json.len() > max_body_size {
let mut left = 0;
let mut right = self.entries.len();
while left < right {
let mid = left + (right - left) / 2;
let mut temp_log = self.clone();
temp_log.entries.truncate(mid);
let temp_entries_json = serde_json::to_string(&temp_log).unwrap_or_default();
if temp_entries_json.len() <= max_body_size {
entries_json = temp_entries_json;
left = mid + 1;
} else {
right = mid;
}
}
}
entries_json
}
pub fn sort_logs(&mut self, sort_order: Sort) {
match sort_order {
Sort::Ascending => self.sort_asc(),
Sort::Descending => self.sort_desc(),
}
}
fn sort_asc(&mut self) {
self.entries.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
}
fn sort_desc(&mut self) {
self.entries.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
}
}
#[cfg(target_family = "wasm")]
macro_rules! ic_cdk_println {
($fmt:expr) => ($crate::debug_print(format!($fmt)));
($fmt:expr, $($arg:tt)*) => ($crate::debug_print(format!($fmt, $($arg)*)));
}
#[cfg(target_family = "wasm")]
fn debug_print<S: std::convert::AsRef<str>>(s: S) {
let s = s.as_ref();
ic0::debug_print(s.as_bytes());
}
#[cfg(not(target_family = "wasm"))]
macro_rules! ic_cdk_println {
($fmt:expr) => (std::println!($fmt));
($fmt:expr, $($arg:tt)*) => (std::println!($fmt, $($arg)*));
}
#[doc(hidden)]
#[derive(Debug)]
pub struct PrintProxySink<Priority: 'static>(pub &'static Priority, pub &'static GlobalBuffer);
impl<Priority: LogPriorityLevels + GetLogFilter> Sink for PrintProxySink<Priority> {
fn append(&self, entry: ic_canister_log::LogEntry) {
let message = format!(
"{} {}:{} {}",
self.0.display_name(),
entry.file,
entry.line,
entry.message,
);
if Priority::get_log_filter().is_match(&message) {
ic_cdk_println!("{}", message);
self.1.append(entry)
}
}
}