Skip to main content

canic_core/
log.rs

1use crate::{
2    ops::{ic::IcOps, runtime::log::LogOps},
3    storage::stable::env::Env,
4};
5use candid::CandidType;
6use serde::{Deserialize, Serialize};
7use std::cell::Cell;
8
9///
10/// Debug
11///
12
13#[derive(
14    Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, CandidType, Deserialize, Serialize,
15)]
16pub enum Level {
17    Debug,
18    Info,
19    Ok,
20    Warn,
21    Error,
22}
23
24impl Level {
25    #[must_use]
26    pub const fn ansi_label(self) -> &'static str {
27        match self {
28            Self::Debug => "DEBUG",
29            Self::Info => "\x1b[34mINFO \x1b[0m",
30            Self::Ok => "\x1b[32m OK  \x1b[0m",
31            Self::Warn => "\x1b[33mWARN \x1b[0m",
32            Self::Error => "\x1b[31mERROR\x1b[0m",
33        }
34    }
35}
36
37///
38/// Topic
39///
40
41#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
42#[remain::sorted]
43pub enum Topic {
44    App,
45    Auth,
46    CanisterLifecycle,
47    CanisterPool,
48    Config,
49    Cycles,
50    Icrc,
51    Init,
52    Memory,
53    Perf,
54    Rpc,
55    Sharding,
56    Sync,
57    Topology,
58    Wasm,
59}
60
61impl Topic {
62    #[must_use]
63    pub const fn as_str(self) -> &'static str {
64        match self {
65            Self::App => "App",
66            Self::Auth => "Auth",
67            Self::CanisterLifecycle => "CanisterLifecycle",
68            Self::CanisterPool => "CanisterPool",
69            Self::Config => "Config",
70            Self::Cycles => "Cycles",
71            Self::Icrc => "Icrc",
72            Self::Init => "Init",
73            Self::Memory => "Memory",
74            Self::Perf => "Perf",
75            Self::Rpc => "Rpc",
76            Self::Sharding => "Sharding",
77            Self::Sync => "Sync",
78            Self::Topology => "Topology",
79            Self::Wasm => "Wasm",
80        }
81    }
82
83    #[must_use]
84    pub const fn log_label(self) -> &'static str {
85        match self {
86            Self::App => "app",
87            Self::Auth => "auth",
88            Self::CanisterLifecycle => "canister_lifecycle",
89            Self::CanisterPool => "canister_pool",
90            Self::Config => "config",
91            Self::Cycles => "cycles",
92            Self::Icrc => "icrc",
93            Self::Init => "init",
94            Self::Memory => "memory",
95            Self::Perf => "perf",
96            Self::Rpc => "rpc",
97            Self::Sharding => "sharding",
98            Self::Sync => "sync",
99            Self::Topology => "topology",
100            Self::Wasm => "wasm",
101        }
102    }
103}
104
105thread_local! {
106    static LOG_READY: Cell<bool> = const { Cell::new(false) };
107}
108
109pub fn set_ready() {
110    LOG_READY.with(|ready| ready.set(true));
111}
112
113#[must_use]
114pub fn is_ready() -> bool {
115    LOG_READY.with(Cell::get)
116}
117
118#[macro_export]
119macro_rules! log {
120    ($topic:expr, $level:ident, $fmt:expr $(, $arg:expr)* $(,)?) => {{
121        $crate::log!(@inner Some($topic), $crate::log::Level::$level, $fmt $(, $arg)*);
122    }};
123
124    ($level:ident, $fmt:expr $(, $arg:expr)* $(,)?) => {{
125        $crate::log!(@inner None::<$crate::log::Topic>, $crate::log::Level::$level, $fmt $(, $arg)*);
126    }};
127
128    (@inner $topic:expr, $level:expr, $fmt:expr $(, $arg:expr)*) => {{
129        if $crate::log::is_ready() {
130            let level = $level;
131            let topic_opt: Option<$crate::log::Topic> = $topic;
132            let message = format!($fmt $(, $arg)*);
133            $crate::log::__emit_runtime_log(env!("CARGO_PKG_NAME"), topic_opt, level, &message);
134        }
135    }};
136}
137
138///
139/// Helpers
140/// (should remain public)
141///
142
143pub fn __append_runtime_log(crate_name: &str, topic: Option<Topic>, level: Level, message: &str) {
144    let created_at = IcOps::now_secs();
145
146    if let Err(err) = LogOps::append_runtime_log(crate_name, topic, level, message, created_at) {
147        #[cfg(debug_assertions)]
148        crate::cdk::println!("log append failed: {err}");
149
150        #[cfg(not(debug_assertions))]
151        let _ = err;
152    }
153}
154
155#[doc(hidden)]
156pub fn __emit_runtime_log(crate_name: &str, topic: Option<Topic>, level: Level, message: &str) {
157    __append_runtime_log(crate_name, topic, level, message);
158
159    let line = __render_runtime_log_line(topic, level, message);
160    crate::cdk::println!("{line}");
161}
162
163#[doc(hidden)]
164#[must_use]
165pub fn __render_runtime_log_line(topic: Option<Topic>, level: Level, message: &str) -> String {
166    let role = __canister_role_label();
167    let topic_prefix = topic.map_or_else(String::new, |topic| format!("[{}] ", topic.as_str()));
168
169    format!(
170        "{}|{:^12}| {}{}",
171        level.ansi_label(),
172        role,
173        topic_prefix,
174        message
175    )
176}
177
178#[doc(hidden)]
179#[must_use]
180pub fn __canister_role_label() -> String {
181    Env::get_canister_role().map_or_else(
182        || "...".to_string(),
183        |role| crate::utils::format::truncate(role.as_str(), 12),
184    )
185}