Next Generation WASM Microkernel Operating System
at main 466 lines 13 kB view raw
1// Copyright 2025 Jonas Kruckenberg 2// 3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or 4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or 5// http://opensource.org/licenses/MIT>, at your option. This file may not be 6// copied, modified, or distributed except according to those terms. 7 8mod color; 9mod filter; 10mod log; 11mod registry; 12mod writer; 13 14use core::cell::{Cell, OnceCell}; 15use core::fmt; 16use core::fmt::Write; 17 18pub use ::tracing::*; 19use color::{Color, SetColor}; 20pub use filter::Filter; 21use k23_cpu_local::cpu_local; 22use k23_spin::OnceLock; 23use registry::Registry; 24use tracing::field; 25use tracing_core::span::{Attributes, Current, Id, Record}; 26use tracing_core::{Collect, Dispatch, Event, Interest, Level, LevelFilter, Metadata}; 27 28use crate::state::try_global; 29use crate::tracing::writer::{MakeWriter, Semihosting}; 30 31static SUBSCRIBER: OnceLock<Subscriber> = OnceLock::new(); 32 33cpu_local! { 34 /// Per-cpu indentation representing the span depth we're currently in 35 static OUTPUT_INDENT: Cell<usize> = Cell::new(0); 36 static CPUID: OnceCell<usize> = OnceCell::new(); 37} 38 39pub fn per_cpu_init_early(cpuid: usize) { 40 CPUID.get_or_init(|| cpuid); 41} 42 43/// Perform early initialization of the tracing subsystem. This will enable printing of `log` and `span` 44/// events, but no spans yet. 45/// 46/// This should be called as early in the boot process as possible. 47pub fn init_early() { 48 let subscriber = SUBSCRIBER.get_or_init(|| Subscriber { 49 // level_filter, 50 output: Output::new(Semihosting::new()), 51 lateinit: OnceLock::new(), 52 }); 53 ::log::set_logger(subscriber).unwrap(); 54 55 let subscriber = SUBSCRIBER.get().unwrap(); 56 let dispatch = Dispatch::from_static(subscriber); 57 dispatch::set_global_default(dispatch).unwrap(); 58} 59 60/// Fully initialize the subsystem, after this point tracing [`Span`]s will be processed as well. 61pub fn init(filter: Filter) { 62 let subscriber = SUBSCRIBER 63 .get() 64 .expect("tracing::init must be called after tracing::init_early"); 65 66 ::log::set_max_level(match filter.max_level() { 67 LevelFilter::OFF => ::log::LevelFilter::Off, 68 LevelFilter::TRACE => ::log::LevelFilter::Trace, 69 LevelFilter::DEBUG => ::log::LevelFilter::Debug, 70 LevelFilter::INFO => ::log::LevelFilter::Info, 71 LevelFilter::WARN => ::log::LevelFilter::Warn, 72 LevelFilter::ERROR => ::log::LevelFilter::Error, 73 }); 74 75 subscriber 76 .lateinit 77 .get_or_init(|| (Registry::default(), filter)); 78} 79 80struct Subscriber { 81 // level_filter: LevelFilter, 82 output: Output<Semihosting>, 83 lateinit: OnceLock<(Registry, Filter)>, 84} 85 86impl Collect for Subscriber { 87 fn register_callsite(&self, meta: &'static Metadata<'static>) -> Interest { 88 if self.enabled(meta) { 89 Interest::always() 90 } else { 91 Interest::never() 92 } 93 } 94 95 fn enabled(&self, meta: &Metadata<'_>) -> bool { 96 if let Some((_, filter)) = self.lateinit.get() { 97 filter.enabled(meta) 98 } else { 99 true 100 } 101 } 102 103 fn max_level_hint(&self) -> Option<LevelFilter> { 104 if let Some((_, filter)) = self.lateinit.get() { 105 Some(filter.max_level()) 106 } else { 107 None 108 } 109 } 110 111 fn new_span(&self, attrs: &Attributes<'_>) -> Id { 112 let Some((registry, _)) = self.lateinit.get() else { 113 tracing::warn!("tracing spans be only be tracked *after* tracing::init is called"); 114 return Id::from_u64(0xDEAD); 115 }; 116 117 let id = registry.new_span(attrs); 118 let meta = attrs.metadata(); 119 120 let Some(mut writer) = self.output.writer(meta) else { 121 return id; 122 }; 123 124 let _ = write_level(&mut writer, *meta.level()); 125 let _ = write_cpu(&mut writer); 126 let _ = write_timestamp(&mut writer); 127 128 // let _ = writer.indent(IndentKind::NewSpan); 129 let _ = write!( 130 writer.with_fg_color(Color::BrightBlack), 131 "{}: ", 132 meta.target() 133 ); 134 let _ = writer.with_bold().write_str(meta.name()); 135 let _ = writer.with_fg_color(Color::BrightBlack).write_str(": "); 136 137 // ensure the span's fields are nicely indented if they wrap by 138 // "entering" and then "exiting"`````findent` 139 // the span. 140 self.output.enter(); 141 attrs.record(&mut Visitor::new(180, &mut writer)); 142 self.output.exit(); 143 144 let _ = writer.write_char('\n'); 145 146 id 147 } 148 149 fn record(&self, _span: &Id, _values: &Record<'_>) { 150 // TODO 151 } 152 153 fn record_follows_from(&self, _span: &Id, _follows: &Id) { 154 // TODO 155 } 156 157 fn event(&self, event: &Event<'_>) { 158 let meta = event.metadata(); 159 160 let Some(mut writer) = self.output.writer(meta) else { 161 return; 162 }; 163 164 let _ = write_level(&mut writer, *meta.level()); 165 let _ = write_cpu(&mut writer); 166 let _ = write_timestamp(&mut writer); 167 // let _ = writer.indent(IndentKind::Event); 168 let _ = write!( 169 writer.with_fg_color(Color::BrightBlack), 170 "{}: ", 171 meta.target() 172 ); 173 event.record(&mut Visitor::new(180, &mut writer)); 174 let _ = writer.write_char('\n'); 175 } 176 177 fn enter(&self, id: &Id) { 178 self.output.enter(); 179 if let Some((registry, _)) = self.lateinit.get() { 180 registry.enter(id); 181 } else { 182 tracing::warn!("tracing spans be only be tracked *after* tracing::init is called"); 183 } 184 } 185 186 fn exit(&self, id: &Id) { 187 self.output.exit(); 188 if let Some((registry, _)) = self.lateinit.get() { 189 registry.exit(id); 190 } else { 191 tracing::warn!("tracing spans be only be tracked *after* tracing::init is called"); 192 } 193 } 194 195 fn clone_span(&self, id: &Id) -> Id { 196 if let Some((registry, _)) = self.lateinit.get() { 197 registry.clone_span(id) 198 } else { 199 tracing::warn!("tracing spans be only be tracked *after* tracing::init is called"); 200 Id::from_u64(0xDEAD) 201 } 202 } 203 204 fn try_close(&self, id: Id) -> bool { 205 if let Some((registry, _)) = self.lateinit.get() { 206 registry.try_close(id) 207 } else { 208 tracing::warn!("tracing spans be only be tracked *after* tracing::init is called"); 209 false 210 } 211 } 212 213 fn current_span(&self) -> Current { 214 if let Some((registry, _)) = self.lateinit.get() { 215 registry.current_span() 216 } else { 217 tracing::warn!("tracing spans be only be tracked *after* tracing::init is called"); 218 Current::none() 219 } 220 } 221} 222 223struct Output<W> { 224 make_writer: W, 225 max_line_len: usize, 226} 227 228impl<W> Output<W> { 229 fn new<'a>(make_writer: W) -> Self 230 where 231 W: MakeWriter<'a>, 232 { 233 Self { 234 max_line_len: make_writer.line_len() - 16, 235 make_writer, 236 } 237 } 238 239 #[inline] 240 fn enabled<'a>(&'a self, metadata: &Metadata<'_>) -> bool 241 where 242 W: MakeWriter<'a>, 243 { 244 self.make_writer.enabled(metadata) 245 } 246 247 #[inline] 248 fn enter(&self) { 249 OUTPUT_INDENT.set(OUTPUT_INDENT.get() + 1); 250 } 251 252 #[inline] 253 fn exit(&self) { 254 let prev = OUTPUT_INDENT.replace(OUTPUT_INDENT.get() - 1); 255 debug_assert!(prev > 0); 256 } 257 258 fn writer<'a>(&'a self, meta: &Metadata<'_>) -> Option<W::Writer> 259 where 260 W: MakeWriter<'a>, 261 { 262 self.make_writer.make_writer_for(meta) 263 264 // Some(Writer { 265 // writer, 266 // current_line: 0, 267 // max_line_len: self.max_line_len, 268 // indent: OUTPUT_INDENT.get(), 269 // }) 270 } 271} 272 273#[inline] 274fn write_level<W>(w: &mut W, level: Level) -> fmt::Result 275where 276 W: Write + SetColor, 277{ 278 w.write_char('[')?; 279 match level { 280 Level::TRACE => w.with_fg_color(Color::Cyan).write_str("TRACE"), 281 Level::DEBUG => w.with_fg_color(Color::Blue).write_str("DEBUG"), 282 Level::INFO => w.with_fg_color(Color::Green).write_str("INFO "), 283 Level::WARN => w.with_fg_color(Color::Yellow).write_str("WARN "), 284 Level::ERROR => { 285 w.set_bold(true); 286 let res = w.with_fg_color(Color::Red).write_str("ERROR"); 287 w.set_bold(false); 288 res 289 } 290 }?; 291 w.write_char(']') 292} 293 294#[inline] 295fn write_cpu<W>(w: &mut W) -> fmt::Result 296where 297 W: Write, 298{ 299 if let Some(cpuid) = CPUID.get() { 300 w.write_fmt(format_args!("[CPU {cpuid}]")) 301 } else { 302 w.write_fmt(format_args!("[CPU ??]")) 303 } 304} 305 306#[inline] 307fn write_timestamp<W>(w: &mut W) -> fmt::Result 308where 309 W: Write + SetColor, 310{ 311 w.write_char('[')?; 312 313 if let Some(global) = try_global() { 314 let elapsed = global.time_origin.elapsed(&global.timer); 315 write!( 316 w.with_fg_color(Color::BrightBlack), 317 "{:>6}.{:06}", 318 elapsed.as_secs(), 319 elapsed.subsec_micros() 320 )?; 321 } else { 322 write!(w.with_fg_color(Color::BrightBlack), " ?.??????")?; 323 } 324 325 w.write_char(']')?; 326 Ok(()) 327} 328 329struct Visitor<'writer, W> { 330 writer: &'writer mut W, 331 seen: bool, 332 newline: bool, 333 comma: bool, 334 max_line_len: usize, 335} 336 337impl<'writer, W> Visitor<'writer, W> 338where 339 W: Write, 340 &'writer mut W: SetColor, 341{ 342 fn new(max_line_len: usize, writer: &'writer mut W) -> Self { 343 Self { 344 writer, 345 seen: false, 346 comma: false, 347 newline: false, 348 max_line_len, 349 } 350 } 351 352 fn record_inner(&mut self, field: &field::Field, val: &dyn fmt::Debug) { 353 // XXX(eliza): sad and gross hack 354 struct HasWrittenNewline<'a, W> { 355 writer: &'a mut W, 356 has_written_newline: bool, 357 has_written_punct: bool, 358 } 359 360 impl<W: Write> Write for HasWrittenNewline<'_, W> { 361 #[inline] 362 fn write_str(&mut self, s: &str) -> fmt::Result { 363 self.has_written_punct = s.ends_with(|ch: char| ch.is_ascii_punctuation()); 364 if s.contains('\n') { 365 self.has_written_newline = true; 366 } 367 self.writer.write_str(s) 368 } 369 } 370 371 impl<W: Write> SetColor for HasWrittenNewline<'_, W> 372 where 373 W: SetColor, 374 { 375 fn fg_color(&self) -> Color { 376 self.writer.fg_color() 377 } 378 379 fn set_fg_color(&mut self, color: Color) { 380 self.writer.set_fg_color(color); 381 } 382 383 fn set_bold(&mut self, bold: bool) { 384 self.writer.set_bold(bold); 385 } 386 } 387 388 let mut writer = HasWrittenNewline { 389 writer: &mut self.writer, 390 has_written_newline: false, 391 has_written_punct: false, 392 }; 393 let nl = if self.newline { '\n' } else { ' ' }; 394 395 if field.name() == "message" { 396 if self.seen { 397 let _ = write!(writer.with_bold(), "{nl}{val:?}"); 398 } else { 399 let _ = write!(writer.with_bold(), "{val:?}"); 400 self.comma = !writer.has_written_punct; 401 } 402 self.seen = true; 403 return; 404 } 405 406 if self.comma { 407 let _ = writer.with_fg_color(Color::BrightBlack).write_char(','); 408 } 409 410 if self.seen { 411 let _ = writer.write_char(nl); 412 } 413 414 if !self.comma { 415 self.seen = true; 416 self.comma = true; 417 } 418 419 // pretty-print the name with dots in the punctuation color 420 let mut name_pieces = field.name().split('.'); 421 if let Some(piece) = name_pieces.next() { 422 let _ = writer.write_str(piece); 423 for piece in name_pieces { 424 let _ = writer.with_fg_color(Color::BrightBlack).write_char('.'); 425 let _ = writer.write_str(piece); 426 } 427 } 428 429 let _ = writer.with_fg_color(Color::BrightBlack).write_char('='); 430 let _ = write!(writer, "{val:?}"); 431 self.newline |= writer.has_written_newline; 432 } 433} 434 435impl<'writer, W> field::Visit for Visitor<'writer, W> 436where 437 W: Write, 438 &'writer mut W: SetColor, 439{ 440 #[inline] 441 fn record_u64(&mut self, field: &field::Field, val: u64) { 442 self.record_inner(field, &val); 443 } 444 445 #[inline] 446 fn record_i64(&mut self, field: &field::Field, val: i64) { 447 self.record_inner(field, &val); 448 } 449 450 #[inline] 451 fn record_bool(&mut self, field: &field::Field, val: bool) { 452 self.record_inner(field, &val); 453 } 454 455 #[inline] 456 fn record_str(&mut self, field: &field::Field, val: &str) { 457 if val.len() >= self.max_line_len { 458 self.newline = true; 459 } 460 self.record_inner(field, &val); 461 } 462 463 fn record_debug(&mut self, field: &field::Field, val: &dyn fmt::Debug) { 464 self.record_inner(field, val); 465 } 466}