devalang_wasm/tools/logger/
mod.rs

1#[cfg(feature = "cli")]
2use crossterm::style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor};
3#[cfg(feature = "cli")]
4use std::fmt::Write;
5
6pub mod rule_checker;
7pub use rule_checker::{RuleChecker, RuleMessage};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum LogLevel {
11    Success,
12    Error,
13    Info,
14    Warning,
15    Watch,
16    Debug,
17    Print,
18    Action,
19}
20
21#[derive(Debug, Clone, Default)]
22pub struct Logger;
23
24impl Logger {
25    pub fn new() -> Self {
26        Self
27    }
28
29    /// Log a rule message with appropriate severity handling
30    pub fn log_rule_message(&self, rule_msg: &RuleMessage) {
31        match rule_msg.level {
32            crate::platform::config::RuleLevel::Error => {
33                self.error(&rule_msg.formatted());
34            }
35            crate::platform::config::RuleLevel::Warning => {
36                self.warn(&rule_msg.formatted());
37            }
38            crate::platform::config::RuleLevel::Info => {
39                self.info(&rule_msg.formatted());
40            }
41            crate::platform::config::RuleLevel::Off => {
42                // Silent
43            }
44        }
45    }
46
47    pub fn log(&self, level: LogLevel, message: impl AsRef<str>) {
48        self.print_line(level, message.as_ref());
49    }
50
51    pub fn log_with_details<I, S>(&self, level: LogLevel, message: impl AsRef<str>, details: I)
52    where
53        I: IntoIterator<Item = S>,
54        S: AsRef<str>,
55    {
56        self.print_line(level, message.as_ref());
57        for detail in details {
58            self.print_detail(detail.as_ref());
59        }
60    }
61
62    /// Log a structured error with formatted details including file location, type, and suggestions
63    pub fn log_structured_error(&self, error: &StructuredError) {
64        self.log(LogLevel::Error, &error.message);
65        let colored_details = error.build_colored_details();
66        for (label, content) in colored_details {
67            self.print_colored_detail(&label, &content);
68        }
69    }
70
71    pub fn success(&self, message: impl AsRef<str>) {
72        self.log(LogLevel::Success, message);
73    }
74
75    pub fn info(&self, message: impl AsRef<str>) {
76        self.log(LogLevel::Info, message);
77    }
78
79    pub fn warn(&self, message: impl AsRef<str>) {
80        self.log(LogLevel::Warning, message);
81    }
82
83    pub fn error(&self, message: impl AsRef<str>) {
84        self.log(LogLevel::Error, message);
85    }
86
87    pub fn watch(&self, message: impl AsRef<str>) {
88        self.log(LogLevel::Watch, message);
89    }
90
91    pub fn debug(&self, message: impl AsRef<str>) {
92        self.log(LogLevel::Debug, message);
93    }
94
95    pub fn action(&self, message: impl AsRef<str>) {
96        self.log(LogLevel::Action, message);
97    }
98
99    /// Print-level messages coming from user `print` statements. Rendered as [PRINT]
100    /// in plain/CLI output. Convenience wrapper around `log`.
101    pub fn print(&self, message: impl AsRef<str>) {
102        self.log(LogLevel::Print, message);
103    }
104
105    fn print_detail(&self, detail: &str) {
106        #[cfg(feature = "cli")]
107        {
108            println!("   ↳ {}", detail);
109        }
110        #[cfg(not(feature = "cli"))]
111        {
112            println!("   -> {}", detail);
113        }
114    }
115
116    /// Print a colored detail line with a label
117    /// Format: "   ↳ label: content" where label is in medium-dark grey
118    #[cfg(feature = "cli")]
119    fn print_colored_detail(&self, label: &str, content: &str) {
120        let mut output = String::new();
121        output.push_str("   ↳ ");
122
123        // Label in medium-dark grey (RGB 110, 110, 110) with bold
124        output.push_str(&format!(
125            "{}{}{}{}",
126            SetForegroundColor(Color::Rgb {
127                r: 110,
128                g: 110,
129                b: 110
130            }),
131            SetAttribute(Attribute::Bold),
132            label,
133            SetAttribute(Attribute::Reset)
134        ));
135
136        // Colon separator
137        output.push_str(": ");
138
139        // Content in normal color (white)
140        output.push_str(&format!("{}{}", SetForegroundColor(Color::White), content));
141
142        output.push_str(&format!("{}", ResetColor));
143
144        println!("{}", output);
145    }
146
147    /// Print a colored detail line with a label (non-CLI version)
148    #[cfg(not(feature = "cli"))]
149    fn print_colored_detail(&self, label: &str, content: &str) {
150        println!("   -> {}: {}", label, content);
151    }
152
153    fn print_line(&self, level: LogLevel, message: &str) {
154        #[cfg(feature = "cli")]
155        {
156            println!("{}", self.render_colored_line(level, message));
157        }
158        #[cfg(not(feature = "cli"))]
159        {
160            println!("[{}] {}", level.as_plain_label(), message);
161        }
162    }
163
164    #[cfg(feature = "cli")]
165    fn render_colored_line(&self, level: LogLevel, message: &str) -> String {
166        let mut out = String::new();
167        let (emoji, color) = level.visuals();
168
169        out.push_str(emoji);
170        out.push(' ');
171        out.push_str(&self.render_signature());
172        out.push(' ');
173        out.push_str(&self.render_status(level, color));
174        out.push(' ');
175        out.push_str(message);
176        out
177    }
178
179    #[cfg(feature = "cli")]
180    fn render_signature(&self) -> String {
181        let mut s = String::new();
182        write!(&mut s, "{}", SetForegroundColor(Color::Grey)).unwrap();
183        s.push('[');
184        write!(
185            &mut s,
186            "{}",
187            SetForegroundColor(Color::Rgb {
188                r: 36,
189                g: 199,
190                b: 181,
191            })
192        )
193        .unwrap();
194        write!(&mut s, "{}", SetAttribute(Attribute::Bold)).unwrap();
195        s.push_str("Devalang");
196        write!(&mut s, "{}", SetAttribute(Attribute::Reset)).unwrap();
197        write!(&mut s, "{}", SetForegroundColor(Color::Grey)).unwrap();
198        s.push(']');
199        write!(&mut s, "{}", ResetColor).unwrap();
200        s
201    }
202
203    #[cfg(feature = "cli")]
204    fn render_status(&self, level: LogLevel, color: Color) -> String {
205        let mut s = String::new();
206        write!(&mut s, "{}", SetForegroundColor(color)).unwrap();
207        write!(&mut s, "{}", SetAttribute(Attribute::Bold)).unwrap();
208        s.push('[');
209        s.push_str(level.as_label());
210        s.push(']');
211        write!(&mut s, "{}", SetAttribute(Attribute::Reset)).unwrap();
212        write!(&mut s, "{}", ResetColor).unwrap();
213        s
214    }
215}
216
217impl LogLevel {
218    fn as_label(self) -> &'static str {
219        match self {
220            LogLevel::Success => "SUCCESS",
221            LogLevel::Error => "ERROR",
222            LogLevel::Info => "INFO",
223            LogLevel::Warning => "WARN",
224            LogLevel::Watch => "WATCH",
225            LogLevel::Debug => "DEBUG",
226            LogLevel::Action => "ACTION",
227            LogLevel::Print => "PRINT",
228        }
229    }
230
231    fn as_plain_label(self) -> &'static str {
232        match self {
233            LogLevel::Success => "SUCCESS",
234            LogLevel::Error => "ERROR",
235            LogLevel::Info => "INFO",
236            LogLevel::Warning => "WARN",
237            LogLevel::Watch => "WATCH",
238            LogLevel::Debug => "DEBUG",
239            LogLevel::Action => "ACTION",
240            LogLevel::Print => "PRINT",
241        }
242    }
243
244    #[cfg(feature = "cli")]
245    fn visuals(self) -> (&'static str, Color) {
246        match self {
247            LogLevel::Success => (
248                "✅",
249                Color::Rgb {
250                    r: 76,
251                    g: 175,
252                    b: 80,
253                },
254            ),
255            LogLevel::Error => (
256                "❌",
257                Color::Rgb {
258                    r: 244,
259                    g: 67,
260                    b: 54,
261                },
262            ),
263            LogLevel::Info => (
264                "ℹ️ ",
265                Color::Rgb {
266                    r: 33,
267                    g: 150,
268                    b: 243,
269                },
270            ),
271            LogLevel::Warning => (
272                "⚠️",
273                Color::Rgb {
274                    r: 255,
275                    g: 152,
276                    b: 0,
277                },
278            ),
279            LogLevel::Watch => (
280                "👀",
281                Color::Rgb {
282                    r: 171,
283                    g: 71,
284                    b: 188,
285                },
286            ),
287            LogLevel::Debug => (
288                "🛠️",
289                Color::Rgb {
290                    r: 121,
291                    g: 134,
292                    b: 203,
293                },
294            ),
295            LogLevel::Action => (
296                "🎵",
297                Color::Rgb {
298                    r: 0,
299                    g: 188,
300                    b: 212,
301                },
302            ),
303            LogLevel::Print => ("", Color::White),
304        }
305    }
306}
307
308pub mod format;
309pub mod layers;
310pub mod sinks;
311pub mod structured_error;
312
313pub use structured_error::StructuredError;