canic_core/
log.rs

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