Skip to main content

canic_core/
log.rs

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