Skip to main content

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        let _ = write!(&mut s, "{}", SetForegroundColor(Color::Grey));
183        s.push('[');
184        let _ = write!(
185            &mut s,
186            "{}",
187            SetForegroundColor(Color::Rgb {
188                r: 36,
189                g: 199,
190                b: 181,
191            })
192        );
193        let _ = write!(&mut s, "{}", SetAttribute(Attribute::Bold));
194        s.push_str("Devalang");
195        let _ = write!(&mut s, "{}", SetAttribute(Attribute::Reset));
196        let _ = write!(&mut s, "{}", SetForegroundColor(Color::Grey));
197        s.push(']');
198        let _ = write!(&mut s, "{}", ResetColor);
199        s
200    }
201
202    #[cfg(feature = "cli")]
203    fn render_status(&self, level: LogLevel, color: Color) -> String {
204        let mut s = String::new();
205        let _ = write!(&mut s, "{}", SetForegroundColor(color));
206        let _ = write!(&mut s, "{}", SetAttribute(Attribute::Bold));
207        s.push('[');
208        s.push_str(level.as_label());
209        s.push(']');
210        let _ = write!(&mut s, "{}", SetAttribute(Attribute::Reset));
211        let _ = write!(&mut s, "{}", ResetColor);
212        s
213    }
214}
215
216impl LogLevel {
217    fn as_label(self) -> &'static str {
218        match self {
219            LogLevel::Success => "SUCCESS",
220            LogLevel::Error => "ERROR",
221            LogLevel::Info => "INFO",
222            LogLevel::Warning => "WARN",
223            LogLevel::Watch => "WATCH",
224            LogLevel::Debug => "DEBUG",
225            LogLevel::Action => "ACTION",
226            LogLevel::Print => "PRINT",
227        }
228    }
229
230    fn as_plain_label(self) -> &'static str {
231        match self {
232            LogLevel::Success => "SUCCESS",
233            LogLevel::Error => "ERROR",
234            LogLevel::Info => "INFO",
235            LogLevel::Warning => "WARN",
236            LogLevel::Watch => "WATCH",
237            LogLevel::Debug => "DEBUG",
238            LogLevel::Action => "ACTION",
239            LogLevel::Print => "PRINT",
240        }
241    }
242
243    #[cfg(feature = "cli")]
244    fn visuals(self) -> (&'static str, Color) {
245        match self {
246            LogLevel::Success => (
247                "✅",
248                Color::Rgb {
249                    r: 76,
250                    g: 175,
251                    b: 80,
252                },
253            ),
254            LogLevel::Error => (
255                "❌",
256                Color::Rgb {
257                    r: 244,
258                    g: 67,
259                    b: 54,
260                },
261            ),
262            LogLevel::Info => (
263                "ℹ️ ",
264                Color::Rgb {
265                    r: 33,
266                    g: 150,
267                    b: 243,
268                },
269            ),
270            LogLevel::Warning => (
271                "⚠️",
272                Color::Rgb {
273                    r: 255,
274                    g: 152,
275                    b: 0,
276                },
277            ),
278            LogLevel::Watch => (
279                "👀",
280                Color::Rgb {
281                    r: 171,
282                    g: 71,
283                    b: 188,
284                },
285            ),
286            LogLevel::Debug => (
287                "🛠️",
288                Color::Rgb {
289                    r: 121,
290                    g: 134,
291                    b: 203,
292                },
293            ),
294            LogLevel::Action => (
295                "🎵",
296                Color::Rgb {
297                    r: 0,
298                    g: 188,
299                    b: 212,
300                },
301            ),
302            LogLevel::Print => ("", Color::White),
303        }
304    }
305}
306
307pub mod format;
308pub mod layers;
309pub mod sinks;
310pub mod structured_error;
311
312pub use structured_error::StructuredError;