1use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10
11#[non_exhaustive]
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub enum BootPhase {
15 Init,
17 Config,
19 Services,
21 ResourceTree,
23 Agents,
25 Network,
27 Ecc,
29 Ready,
31}
32
33impl BootPhase {
34 pub fn tag(&self) -> &'static str {
36 match self {
37 BootPhase::Init => "INIT",
38 BootPhase::Config => "CONFIG",
39 BootPhase::Services => "SERVICES",
40 BootPhase::ResourceTree => "TREE",
41 BootPhase::Agents => "AGENTS",
42 BootPhase::Network => "NETWORK",
43 BootPhase::Ecc => "ECC",
44 BootPhase::Ready => "READY",
45 }
46 }
47}
48
49impl std::fmt::Display for BootPhase {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 write!(f, "{}", self.tag())
52 }
53}
54
55#[non_exhaustive]
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58pub enum LogLevel {
59 Debug,
61 Info,
63 Warn,
65 Error,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct BootEvent {
72 pub timestamp: DateTime<Utc>,
74 pub phase: BootPhase,
76 pub message: String,
78 pub level: LogLevel,
80}
81
82impl BootEvent {
83 pub fn info(phase: BootPhase, message: impl Into<String>) -> Self {
85 Self {
86 timestamp: Utc::now(),
87 phase,
88 message: message.into(),
89 level: LogLevel::Info,
90 }
91 }
92
93 pub fn warn(phase: BootPhase, message: impl Into<String>) -> Self {
95 Self {
96 timestamp: Utc::now(),
97 phase,
98 message: message.into(),
99 level: LogLevel::Warn,
100 }
101 }
102
103 pub fn error(phase: BootPhase, message: impl Into<String>) -> Self {
105 Self {
106 timestamp: Utc::now(),
107 phase,
108 message: message.into(),
109 level: LogLevel::Error,
110 }
111 }
112
113 pub fn format_line(&self) -> String {
117 let tag = self.phase.tag();
118 format!(" [{tag:<10}] {}", self.message)
119 }
120}
121
122#[derive(Debug, Clone, Default)]
127pub struct BootLog {
128 events: Vec<BootEvent>,
129}
130
131impl BootLog {
132 pub fn new() -> Self {
134 Self { events: Vec::new() }
135 }
136
137 pub fn push(&mut self, event: BootEvent) {
139 self.events.push(event);
140 }
141
142 pub fn events(&self) -> &[BootEvent] {
144 &self.events
145 }
146
147 pub fn format_all(&self) -> String {
149 let mut output = String::new();
150 for event in &self.events {
151 if event.level == LogLevel::Debug {
152 continue;
153 }
154 output.push_str(&event.format_line());
155 output.push('\n');
156 }
157 output
158 }
159
160 pub fn len(&self) -> usize {
162 self.events.len()
163 }
164
165 pub fn is_empty(&self) -> bool {
167 self.events.is_empty()
168 }
169}
170
171const DEFAULT_EVENT_LOG_CAPACITY: usize = 1024;
173
174pub struct KernelEventLog {
182 events: std::sync::Mutex<std::collections::VecDeque<BootEvent>>,
183 capacity: usize,
184}
185
186impl KernelEventLog {
187 pub fn new() -> Self {
189 Self::with_capacity(DEFAULT_EVENT_LOG_CAPACITY)
190 }
191
192 pub fn with_capacity(capacity: usize) -> Self {
194 Self {
195 events: std::sync::Mutex::new(std::collections::VecDeque::with_capacity(capacity)),
196 capacity,
197 }
198 }
199
200 pub fn push(&self, event: BootEvent) {
202 let mut events = self.events.lock().unwrap();
203 if events.len() >= self.capacity {
204 events.pop_front();
205 }
206 events.push_back(event);
207 }
208
209 pub fn info(&self, source: &str, message: impl Into<String>) {
211 self.push(BootEvent {
212 timestamp: Utc::now(),
213 phase: BootPhase::Ready, message: format!("[{source}] {}", message.into()),
215 level: LogLevel::Info,
216 });
217 }
218
219 pub fn warn(&self, source: &str, message: impl Into<String>) {
221 self.push(BootEvent {
222 timestamp: Utc::now(),
223 phase: BootPhase::Ready,
224 message: format!("[{source}] {}", message.into()),
225 level: LogLevel::Warn,
226 });
227 }
228
229 pub fn error(&self, source: &str, message: impl Into<String>) {
231 self.push(BootEvent {
232 timestamp: Utc::now(),
233 phase: BootPhase::Ready,
234 message: format!("[{source}] {}", message.into()),
235 level: LogLevel::Error,
236 });
237 }
238
239 #[cfg(feature = "exochain")]
241 pub fn info_with_chain(
242 &self,
243 source: &str,
244 message: impl Into<String>,
245 chain: Option<&crate::chain::ChainManager>,
246 ) {
247 let msg = message.into();
248 self.info(source, &msg);
249 if let Some(cm) = chain {
250 cm.append(source, "log.info", Some(serde_json::json!({ "message": msg })));
251 }
252 }
253
254 pub fn ingest_boot_log(&self, boot_log: &BootLog) {
256 for event in boot_log.events() {
257 self.push(event.clone());
258 }
259 }
260
261 pub fn tail(&self, n: usize) -> Vec<BootEvent> {
263 let events = self.events.lock().unwrap();
264 if n == 0 || n >= events.len() {
265 events.iter().cloned().collect()
266 } else {
267 events.iter().skip(events.len() - n).cloned().collect()
268 }
269 }
270
271 pub fn filter_level(&self, min_level: &LogLevel, n: usize) -> Vec<BootEvent> {
273 let events = self.events.lock().unwrap();
274 let level_rank = |l: &LogLevel| -> u8 {
275 match l {
276 LogLevel::Debug => 0,
277 LogLevel::Info => 1,
278 LogLevel::Warn => 2,
279 LogLevel::Error => 3,
280 }
281 };
282 let min_rank = level_rank(min_level);
283 let filtered: Vec<BootEvent> = events
284 .iter()
285 .filter(|e| level_rank(&e.level) >= min_rank)
286 .cloned()
287 .collect();
288 if n == 0 || n >= filtered.len() {
289 filtered
290 } else {
291 filtered[filtered.len() - n..].to_vec()
292 }
293 }
294
295 pub fn len(&self) -> usize {
297 self.events.lock().unwrap().len()
298 }
299
300 pub fn is_empty(&self) -> bool {
302 self.events.lock().unwrap().is_empty()
303 }
304}
305
306impl Default for KernelEventLog {
307 fn default() -> Self {
308 Self::new()
309 }
310}
311
312impl std::fmt::Debug for KernelEventLog {
313 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
314 let len = self.len();
315 f.debug_struct("KernelEventLog")
316 .field("count", &len)
317 .field("capacity", &self.capacity)
318 .finish()
319 }
320}
321
322pub fn boot_banner() -> String {
324 let mut output = String::new();
325 output.push_str("\n WeftOS v0.1.0\n");
326 output.push_str(" ");
327 output.push_str(&"-".repeat(45));
328 output.push('\n');
329 output
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335
336 #[test]
337 fn boot_phase_tags() {
338 assert_eq!(BootPhase::Init.tag(), "INIT");
339 assert_eq!(BootPhase::Config.tag(), "CONFIG");
340 assert_eq!(BootPhase::Services.tag(), "SERVICES");
341 assert_eq!(BootPhase::ResourceTree.tag(), "TREE");
342 assert_eq!(BootPhase::Agents.tag(), "AGENTS");
343 assert_eq!(BootPhase::Network.tag(), "NETWORK");
344 assert_eq!(BootPhase::Ready.tag(), "READY");
345 }
346
347 #[test]
348 fn boot_event_info() {
349 let event = BootEvent::info(BootPhase::Init, "WeftOS v0.1.0 booting...");
350 assert_eq!(event.phase, BootPhase::Init);
351 assert_eq!(event.level, LogLevel::Info);
352 assert_eq!(event.message, "WeftOS v0.1.0 booting...");
353 }
354
355 #[test]
356 fn boot_event_format_line() {
357 let event = BootEvent::info(BootPhase::Init, "PID 0 (kernel)");
358 let line = event.format_line();
359 assert!(line.contains("[INIT"));
360 assert!(line.contains("PID 0 (kernel)"));
361 }
362
363 #[test]
364 fn boot_log_push_and_format() {
365 let mut log = BootLog::new();
366 log.push(BootEvent::info(BootPhase::Init, "booting..."));
367 log.push(BootEvent::info(BootPhase::Config, "config loaded"));
368 log.push(BootEvent::info(BootPhase::Ready, "ready"));
369
370 assert_eq!(log.len(), 3);
371 let formatted = log.format_all();
372 assert!(formatted.contains("booting..."));
373 assert!(formatted.contains("config loaded"));
374 assert!(formatted.contains("ready"));
375 }
376
377 #[test]
378 fn boot_log_skips_debug() {
379 let mut log = BootLog::new();
380 log.push(BootEvent {
381 timestamp: Utc::now(),
382 phase: BootPhase::Init,
383 message: "debug msg".into(),
384 level: LogLevel::Debug,
385 });
386 log.push(BootEvent::info(BootPhase::Init, "info msg"));
387
388 let formatted = log.format_all();
389 assert!(!formatted.contains("debug msg"));
390 assert!(formatted.contains("info msg"));
391 }
392
393 #[test]
394 fn boot_log_empty() {
395 let log = BootLog::new();
396 assert!(log.is_empty());
397 assert_eq!(log.len(), 0);
398 assert!(log.format_all().is_empty());
399 }
400
401 #[test]
402 fn boot_banner_format() {
403 let banner = boot_banner();
404 assert!(banner.contains("WeftOS v0.1.0"));
405 assert!(banner.contains("---"));
406 }
407
408 #[test]
409 fn boot_event_serde() {
410 let event = BootEvent::info(BootPhase::Services, "[OK] message-bus");
411 let json = serde_json::to_string(&event).unwrap();
412 let restored: BootEvent = serde_json::from_str(&json).unwrap();
413 assert_eq!(restored.phase, BootPhase::Services);
414 assert_eq!(restored.message, "[OK] message-bus");
415 }
416
417 #[test]
418 fn boot_event_warn_and_error() {
419 let warn_event = BootEvent::warn(BootPhase::Services, "slow start");
420 assert_eq!(warn_event.level, LogLevel::Warn);
421
422 let err_event = BootEvent::error(BootPhase::Services, "failed");
423 assert_eq!(err_event.level, LogLevel::Error);
424 }
425
426 #[test]
429 fn event_log_push_and_tail() {
430 let log = KernelEventLog::new();
431 log.info("test", "first");
432 log.info("test", "second");
433 log.warn("test", "third");
434
435 assert_eq!(log.len(), 3);
436
437 let all = log.tail(0);
438 assert_eq!(all.len(), 3);
439 assert!(all[0].message.contains("first"));
440
441 let last_two = log.tail(2);
442 assert_eq!(last_two.len(), 2);
443 assert!(last_two[0].message.contains("second"));
444 }
445
446 #[test]
447 fn event_log_ring_buffer_evicts() {
448 let log = KernelEventLog::with_capacity(3);
449 log.info("a", "1");
450 log.info("b", "2");
451 log.info("c", "3");
452 log.info("d", "4"); assert_eq!(log.len(), 3);
455 let all = log.tail(0);
456 assert!(all[0].message.contains("[b] 2"));
457 assert!(all[2].message.contains("[d] 4"));
458 }
459
460 #[test]
461 fn event_log_filter_level() {
462 let log = KernelEventLog::new();
463 log.info("test", "info msg");
464 log.warn("test", "warn msg");
465 log.error("test", "error msg");
466
467 let warns_and_above = log.filter_level(&LogLevel::Warn, 0);
468 assert_eq!(warns_and_above.len(), 2);
469
470 let errors_only = log.filter_level(&LogLevel::Error, 0);
471 assert_eq!(errors_only.len(), 1);
472 }
473
474 #[test]
475 fn event_log_ingest_boot_log() {
476 let mut boot_log = BootLog::new();
477 boot_log.push(BootEvent::info(BootPhase::Init, "booting"));
478 boot_log.push(BootEvent::info(BootPhase::Ready, "ready"));
479
480 let event_log = KernelEventLog::new();
481 event_log.ingest_boot_log(&boot_log);
482
483 assert_eq!(event_log.len(), 2);
484 let events = event_log.tail(0);
485 assert!(events[0].message.contains("booting"));
486 }
487
488 #[test]
489 fn event_log_empty() {
490 let log = KernelEventLog::new();
491 assert!(log.is_empty());
492 assert_eq!(log.len(), 0);
493 assert!(log.tail(10).is_empty());
494 }
495}