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#[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#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
41#[remain::sorted]
42pub enum Topic {
43 Auth,
44 CanisterLifecycle,
45 Config,
46 Cycles,
47 Fleet,
48 Icrc,
49 Init,
50 Memory,
51 Perf,
52 Rpc,
53 Sharding,
54 Sync,
55 Topology,
56 Wasm,
57}
58
59impl Topic {
60 #[must_use]
61 pub const fn as_str(self) -> &'static str {
62 match self {
63 Self::Auth => "Auth",
64 Self::CanisterLifecycle => "CanisterLifecycle",
65 Self::Config => "Config",
66 Self::Cycles => "Cycles",
67 Self::Fleet => "Fleet",
68 Self::Icrc => "Icrc",
69 Self::Init => "Init",
70 Self::Memory => "Memory",
71 Self::Perf => "Perf",
72 Self::Rpc => "Rpc",
73 Self::Sharding => "Sharding",
74 Self::Sync => "Sync",
75 Self::Topology => "Topology",
76 Self::Wasm => "Wasm",
77 }
78 }
79
80 #[must_use]
81 pub const fn log_label(self) -> &'static str {
82 match self {
83 Self::Auth => "auth",
84 Self::CanisterLifecycle => "canister_lifecycle",
85 Self::Config => "config",
86 Self::Cycles => "cycles",
87 Self::Fleet => "fleet",
88 Self::Icrc => "icrc",
89 Self::Init => "init",
90 Self::Memory => "memory",
91 Self::Perf => "perf",
92 Self::Rpc => "rpc",
93 Self::Sharding => "sharding",
94 Self::Sync => "sync",
95 Self::Topology => "topology",
96 Self::Wasm => "wasm",
97 }
98 }
99}
100
101thread_local! {
102 static LOG_READY: Cell<bool> = const { Cell::new(false) };
103}
104
105pub fn set_ready() {
106 LOG_READY.with(|ready| ready.set(true));
107}
108
109#[must_use]
110pub fn is_ready() -> bool {
111 LOG_READY.with(Cell::get)
112}
113
114#[macro_export]
115macro_rules! log {
116 ($topic:expr, $level:ident, $fmt:expr $(, $arg:expr)* $(,)?) => {{
117 $crate::log!(@inner Some($topic), $crate::log::Level::$level, $fmt $(, $arg)*);
118 }};
119
120 ($level:ident, $fmt:expr $(, $arg:expr)* $(,)?) => {{
121 $crate::log!(@inner None::<$crate::log::Topic>, $crate::log::Level::$level, $fmt $(, $arg)*);
122 }};
123
124 (@inner $topic:expr, $level:expr, $fmt:expr $(, $arg:expr)*) => {{
125 if $crate::log::is_ready() {
126 let level = $level;
127 let topic_opt: Option<$crate::log::Topic> = $topic;
128 let message = format!($fmt $(, $arg)*);
129 $crate::log::__emit_runtime_log(env!("CARGO_PKG_NAME"), topic_opt, level, &message);
130 }
131 }};
132}
133
134pub fn __append_runtime_log(crate_name: &str, topic: Option<Topic>, level: Level, message: &str) {
141 let created_at = IcOps::now_secs();
142
143 if let Err(err) =
144 LogRetentionWorkflow::append_runtime_log(crate_name, topic, level, message, created_at)
145 {
146 #[cfg(debug_assertions)]
147 ic_cdk::println!("log append failed: {err}");
148
149 #[cfg(not(debug_assertions))]
150 let _ = err;
151 }
152}
153
154#[doc(hidden)]
155pub fn __emit_runtime_log(crate_name: &str, topic: Option<Topic>, level: Level, message: &str) {
156 __append_runtime_log(crate_name, topic, level, message);
157
158 let line = __render_runtime_log_line(topic, level, message);
159 ic_cdk::println!("{line}");
160}
161
162#[doc(hidden)]
163#[must_use]
164pub fn __render_runtime_log_line(topic: Option<Topic>, level: Level, message: &str) -> String {
165 let role = __canister_role_label();
166 let topic_prefix = topic.map_or_else(String::new, |topic| format!("[{}] ", topic.as_str()));
167
168 format!(
169 "{}|{:^12}| {}{}",
170 level.ansi_label(),
171 role,
172 topic_prefix,
173 message
174 )
175}
176
177#[doc(hidden)]
178#[must_use]
179pub fn __canister_role_label() -> String {
180 Env::get_canister_role().map_or_else(
181 || "...".to_string(),
182 |role| crate::format::truncate(role.as_str(), 12),
183 )
184}