canic_core/
log.rs

1use crate::{
2    ops::{ic::IcOps, runtime::log::LogOps},
3    storage::stable::env::Env,
4};
5use candid::CandidType;
6use derive_more::Display;
7use serde::{Deserialize, Serialize};
8use std::cell::Cell;
9
10///
11/// Debug
12///
13
14#[derive(
15    Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, CandidType, Display, Serialize, Deserialize,
16)]
17pub enum Level {
18    Debug, // least severe
19    Info,
20    Ok,
21    Warn,
22    Error, // most severe
23}
24
25///
26/// Topic
27///
28
29#[derive(Clone, Copy, Display, Eq, PartialEq)]
30#[remain::sorted]
31pub enum Topic {
32    App,
33    Auth,
34    CanisterLifecycle,
35    CanisterPool,
36    Config,
37    Cycles,
38    Icrc,
39    Init,
40    Memory,
41    Perf,
42    Rpc,
43    Sharding,
44    Sync,
45    Topology,
46    Wasm,
47}
48
49thread_local! {
50    static LOG_READY: Cell<bool> = const { Cell::new(false) };
51}
52
53pub fn set_ready() {
54    LOG_READY.with(|ready| ready.set(true));
55}
56
57#[must_use]
58pub fn is_ready() -> bool {
59    LOG_READY.with(Cell::get)
60}
61
62#[macro_export]
63macro_rules! log {
64    // =========================================
65    // (1) With topic (normal + trailing comma)
66    // =========================================
67    ($topic:expr, $level:ident, $fmt:expr $(, $arg:expr)* $(,)?) => {{
68        $crate::log!(@inner Some(&$topic.to_string()), $crate::log::Level::$level, $fmt $(, $arg)*);
69    }};
70
71    // =========================================
72    // (2) No topic (normal + trailing comma)
73    // =========================================
74    ($level:ident, $fmt:expr $(, $arg:expr)* $(,)?) => {{
75        $crate::log!(@inner None::<&str>, $crate::log::Level::$level, $fmt $(, $arg)*);
76    }};
77
78    // =========================================
79    // INTERNAL
80    // =========================================
81    (@inner $topic:expr, $level:expr, $fmt:expr $(, $arg:expr)*) => {{
82        if $crate::log::is_ready() {
83            let level = $level;
84            let topic_opt: Option<&str> = $topic;
85            let message = format!($fmt $(, $arg)*);
86
87            // append entry
88            let crate_name = env!("CARGO_PKG_NAME");
89            $crate::log::__append_runtime_log(crate_name, topic_opt, level, &message);
90
91            let ty_raw = $crate::log::__canister_role_string().unwrap_or_else(|| "...".to_string());
92
93            let ty_disp = $crate::utils::format::ellipsize_middle(&ty_raw, 9, 4, 4);
94            let ty_centered = format!("{:^9}", ty_disp);
95
96            let final_msg = if let Some(t) = topic_opt {
97                format!("[{t}] {message}")
98            } else {
99                message
100            };
101
102            let (color, reset) = match level {
103                $crate::log::Level::Ok    => ("\x1b[32m", "\x1b[0m"),
104                $crate::log::Level::Info  => ("\x1b[34m", "\x1b[0m"),
105                $crate::log::Level::Warn  => ("\x1b[33m", "\x1b[0m"),
106                $crate::log::Level::Error => ("\x1b[31m", "\x1b[0m"),
107                $crate::log::Level::Debug => ("", ""),
108            };
109
110            let label = format!("{color}{:^5}{reset}", level.to_string().to_uppercase());
111            let line = format!("{label}|{ty_centered}| {final_msg}");
112
113            $crate::cdk::println!("{line}");
114        }
115    }};
116}
117
118///
119/// Helpers
120/// (should remain public)
121///
122
123pub fn __append_runtime_log(crate_name: &str, topic: Option<&str>, level: Level, message: &str) {
124    let created_at = IcOps::now_secs();
125
126    if let Err(err) = LogOps::append_runtime_log(crate_name, topic, level, message, created_at) {
127        {
128            #[cfg(debug_assertions)]
129            crate::cdk::println!("log append failed: {err}");
130        }
131    }
132}
133
134#[doc(hidden)]
135#[must_use]
136pub fn __canister_role_string() -> Option<String> {
137    Env::get_canister_role().map(|role| role.to_string())
138}