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#[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#[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
134 let crate_name = env!("CARGO_PKG_NAME");
135 $crate::log::__append_runtime_log(crate_name, topic_opt, level, &message);
136
137 let ty_centered = format!("{:^12}", $crate::log::__canister_role_label());
138
139 let final_msg = if let Some(t) = topic_opt {
140 format!("[{}] {message}", t.as_str())
141 } else {
142 message
143 };
144
145 let label = level.ansi_label();
146 let line = format!("{label}|{ty_centered}| {final_msg}");
147
148 $crate::cdk::println!("{line}");
149 }
150 }};
151}
152
153pub fn __append_runtime_log(crate_name: &str, topic: Option<Topic>, level: Level, message: &str) {
159 let created_at = IcOps::now_secs();
160
161 if let Err(err) = LogOps::append_runtime_log(crate_name, topic, level, message, created_at) {
162 #[cfg(debug_assertions)]
163 crate::cdk::println!("log append failed: {err}");
164
165 #[cfg(not(debug_assertions))]
166 let _ = err;
167 }
168}
169
170#[doc(hidden)]
171#[must_use]
172pub fn __canister_role_label() -> String {
173 Env::get_canister_role().map_or_else(
174 || "...".to_string(),
175 |role| crate::utils::format::truncate(role.as_str(), 12),
176 )
177}