1#![doc(html_logo_url = "https://raw.githubusercontent.com/emit-rs/emit/main/asset/logo.svg")]
86#![deny(missing_docs)]
87
88use std::{cell::RefCell, cmp, fmt, io::Write, iter, str, time::Duration};
89
90use emit::well_known::{
91 KEY_ERR, KEY_EVT_KIND, KEY_LVL, KEY_METRIC_VALUE, KEY_SPAN_ID, KEY_TRACE_ID,
92};
93use termcolor::{Buffer, BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
94
95pub fn stdout() -> Stdout {
101 Stdout::new()
102}
103
104pub fn stderr() -> Stderr {
110 Stderr::new()
111}
112
113pub struct Stdout {
131 writer: Writer,
132}
133
134impl Stdout {
135 pub fn new() -> Self {
141 Stdout {
142 writer: Writer {
143 writer: BufferWriter::stdout(ColorChoice::Auto),
144 },
145 }
146 }
147
148 pub fn colored(mut self, colored: bool) -> Self {
154 if colored {
155 self.writer = Writer {
156 writer: BufferWriter::stdout(ColorChoice::Always),
157 };
158 } else {
159 self.writer = Writer {
160 writer: BufferWriter::stdout(ColorChoice::Never),
161 };
162 }
163
164 self
165 }
166}
167
168impl emit::emitter::Emitter for Stdout {
169 fn emit<E: emit::event::ToEvent>(&self, evt: E) {
170 self.writer.emit(evt)
171 }
172
173 fn blocking_flush(&self, _: Duration) -> bool {
174 true
175 }
176}
177
178impl emit::runtime::InternalEmitter for Stdout {}
179
180pub struct Stderr {
198 writer: Writer,
199}
200
201impl Stderr {
202 pub fn new() -> Self {
208 Stderr {
209 writer: Writer {
210 writer: BufferWriter::stderr(ColorChoice::Auto),
211 },
212 }
213 }
214
215 pub fn colored(mut self, colored: bool) -> Self {
221 if colored {
222 self.writer = Writer {
223 writer: BufferWriter::stderr(ColorChoice::Always),
224 };
225 } else {
226 self.writer = Writer {
227 writer: BufferWriter::stderr(ColorChoice::Never),
228 };
229 }
230
231 self
232 }
233}
234
235impl emit::emitter::Emitter for Stderr {
236 fn emit<E: emit::event::ToEvent>(&self, evt: E) {
237 self.writer.emit(evt)
238 }
239
240 fn blocking_flush(&self, _: Duration) -> bool {
241 true
242 }
243}
244
245impl emit::runtime::InternalEmitter for Stderr {}
246
247struct Writer {
248 writer: BufferWriter,
249}
250
251impl Writer {
252 fn emit<E: emit::event::ToEvent>(&self, evt: E) {
253 let evt = evt.to_event();
254
255 with_shared_buf(&self.writer, |writer, buf| {
256 write_event(buf, evt);
257
258 let _ = writer.print(buf);
259 });
260 }
261}
262
263fn write_event(buf: &mut Buffer, evt: emit::event::Event<impl emit::props::Props>) {
264 if let Some(span_id) = evt.props().pull::<emit::SpanId, _>(KEY_SPAN_ID) {
265 if let Some(trace_id) = evt.props().pull::<emit::TraceId, _>(KEY_TRACE_ID) {
266 let trace_id_color = trace_id_color(&trace_id);
267
268 write_fg(buf, "▓", Color::Ansi256(trace_id_color));
269 write_plain(buf, " ");
270 write_plain(buf, hex_slice(&trace_id.to_hex(), 6));
271 write_plain(buf, " ");
272 } else {
273 write_plain(buf, "░ ");
274 }
275
276 let span_id_color = span_id_color(&span_id);
277
278 write_fg(buf, "▓", Color::Ansi256(span_id_color));
279 write_plain(buf, " ");
280 write_plain(buf, hex_slice(&span_id.to_hex(), 4));
281 write_plain(buf, " ");
282 }
283
284 if let Some(extent) = evt.extent() {
285 if let Some(len) = extent.len() {
286 write_timestamp(buf, *extent.as_point());
287 write_plain(buf, " ");
288 write_duration(buf, len);
289 } else if let Some(range) = extent.as_range() {
290 write_timestamp(buf, range.start);
291 write_plain(buf, "..");
292 write_timestamp(buf, range.end);
293 } else {
294 write_timestamp(buf, *extent.as_point());
295 }
296
297 write_plain(buf, " ");
298 }
299
300 let mut lvl = None;
301 if let Some(level) = evt.props().pull::<emit::Level, _>(KEY_LVL) {
302 lvl = level_color(&level).map(Color::Ansi256);
303
304 try_write_fg(buf, level, lvl);
305 write_plain(buf, " ");
306 }
307
308 if let Some(kind) = evt.props().get(KEY_EVT_KIND) {
309 write_fg(buf, kind, KIND);
310 write_plain(buf, " ");
311 }
312
313 let mut mdl = evt.mdl().segments();
314 if let (Some(first), last) = (mdl.next(), mdl.last()) {
315 write_fg(buf, first, MDL_FIRST);
316 write_plain(buf, " ");
317
318 if let Some(last) = last {
319 write_fg(buf, last, MDL_LAST);
320 write_plain(buf, " ");
321 }
322 }
323
324 let _ = evt.msg().write(TokenWriter { buf });
325 write_plain(buf, "\n");
326
327 if let Some(err) = evt.props().get(KEY_ERR) {
328 if let Some(err) = err.to_borrowed_error() {
329 write_plain(buf, " ");
330 try_write_fg(buf, "err", lvl);
331 write_plain(buf, format_args!(": {err}\n"));
332
333 for cause in iter::successors(err.source(), |err| (*err).source()) {
334 write_plain(buf, " ");
335 try_write_fg(buf, "caused by", lvl);
336 write_plain(buf, format_args!(": {cause}\n"));
337 }
338 }
339 }
340
341 if let Some(value) = evt.props().get(KEY_METRIC_VALUE) {
342 let buckets = value.to_f64_sequence().unwrap_or_default();
343
344 if !buckets.is_empty() {
345 write_timeseries(buf, &buckets);
346 }
347 }
348}
349
350fn write_timeseries(buf: &mut Buffer, buckets: &[f64]) {
351 const BLOCKS: [&'static str; 7] = ["▁", "▂", "▃", "▄", "▅", "▆", "▇"];
352
353 let mut bucket_min = f64::NAN;
354 let mut bucket_max = -f64::NAN;
355
356 for v in buckets {
357 bucket_min = cmp::min_by(*v, bucket_min, f64::total_cmp);
358 bucket_max = cmp::max_by(*v, bucket_max, f64::total_cmp);
359 }
360
361 for v in buckets {
362 let idx = (((v - bucket_min) / (bucket_max - bucket_min)) * ((BLOCKS.len() - 1) as f64))
363 .ceil() as usize;
364 let _ = buf.write(BLOCKS[idx].as_bytes());
365 }
366
367 let _ = buf.write(b"\n");
368}
369
370fn hex_slice<'a>(hex: &'a [u8], len: usize) -> impl fmt::Display + 'a {
371 struct HexSlice<'a>(&'a [u8], usize);
372
373 impl<'a> fmt::Display for HexSlice<'a> {
374 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
375 f.write_str(str::from_utf8(&self.0[..self.1]).unwrap())
376 }
377 }
378
379 HexSlice(hex, len)
380}
381
382struct LocalTime {
383 h: u8,
384 m: u8,
385 s: u8,
386 ms: u16,
387}
388
389fn local_ts(ts: emit::Timestamp) -> Option<LocalTime> {
390 #[cfg(test)]
391 {
392 let _ = ts;
395
396 None
397 }
398 #[cfg(not(test))]
399 {
400 let local = time::OffsetDateTime::from_unix_timestamp_nanos(
407 ts.to_unix().as_nanos().try_into().ok()?,
408 )
409 .ok()?;
410 let local = local.checked_to_offset(time::UtcOffset::local_offset_at(local).ok()?)?;
411
412 let (h, m, s, ms) = local.time().as_hms_milli();
413
414 Some(LocalTime { h, m, s, ms })
415 }
416}
417
418fn write_timestamp(buf: &mut Buffer, ts: emit::Timestamp) {
419 if let Some(LocalTime { h, m, s, ms }) = local_ts(ts) {
420 write_plain(
421 buf,
422 format_args!("{:>02}:{:>02}:{:>02}.{:>03}", h, m, s, ms),
423 );
424 } else {
425 write_plain(buf, format_args!("{:.0}", ts));
426 }
427}
428
429struct FriendlyDuration {
430 pub value: u128,
431 pub unit: &'static str,
432}
433
434fn friendly_duration(duration: Duration) -> FriendlyDuration {
435 const NANOS_PER_MICRO: u128 = 1000;
436 const NANOS_PER_MILLI: u128 = NANOS_PER_MICRO * 1000;
437 const NANOS_PER_SEC: u128 = NANOS_PER_MILLI * 1000;
438 const NANOS_PER_MIN: u128 = NANOS_PER_SEC * 60;
439
440 let nanos = duration.as_nanos();
441
442 if nanos < NANOS_PER_MICRO * 2 {
443 FriendlyDuration {
444 value: nanos,
445 unit: "ns",
446 }
447 } else if nanos < NANOS_PER_MILLI * 2 {
448 FriendlyDuration {
449 value: nanos / NANOS_PER_MICRO,
450 unit: "μs",
451 }
452 } else if nanos < NANOS_PER_SEC * 2 {
453 FriendlyDuration {
454 value: nanos / NANOS_PER_MILLI,
455 unit: "ms",
456 }
457 } else if nanos < NANOS_PER_MIN * 2 {
458 FriendlyDuration {
459 value: nanos / NANOS_PER_SEC,
460 unit: "s",
461 }
462 } else {
463 FriendlyDuration {
464 value: nanos / NANOS_PER_MIN,
465 unit: "m",
466 }
467 }
468}
469
470fn write_duration(buf: &mut Buffer, duration: Duration) {
471 let FriendlyDuration { value, unit } = friendly_duration(duration);
472
473 write_fg(buf, value, NUMBER);
474 write_fg(buf, unit, TEXT);
475}
476
477struct TokenWriter<'a> {
478 buf: &'a mut Buffer,
479}
480
481impl<'a> sval_fmt::TokenWrite for TokenWriter<'a> {
482 fn write_text_quote(&mut self) -> fmt::Result {
483 Ok(())
484 }
485
486 fn write_text(&mut self, text: &str) -> fmt::Result {
487 self.write(text, TEXT);
488
489 Ok(())
490 }
491
492 fn write_number<N: fmt::Display>(&mut self, num: N) -> fmt::Result {
493 self.write(num, NUMBER);
494
495 Ok(())
496 }
497
498 fn write_atom<A: fmt::Display>(&mut self, atom: A) -> fmt::Result {
499 self.write(atom, ATOM);
500
501 Ok(())
502 }
503
504 fn write_ident(&mut self, ident: &str) -> fmt::Result {
505 self.write(ident, IDENT);
506
507 Ok(())
508 }
509
510 fn write_field(&mut self, field: &str) -> fmt::Result {
511 self.write(field, FIELD);
512
513 Ok(())
514 }
515}
516
517impl<'a> fmt::Write for TokenWriter<'a> {
518 fn write_str(&mut self, s: &str) -> fmt::Result {
519 write!(&mut self.buf, "{}", s).map_err(|_| fmt::Error)
520 }
521}
522
523impl<'a> emit::template::Write for TokenWriter<'a> {
524 fn write_hole_value(&mut self, _: &str, value: emit::Value) -> fmt::Result {
525 sval_fmt::stream_to_token_write(self, value)
526 }
527
528 fn write_hole_fmt(
529 &mut self,
530 _: &str,
531 value: emit::Value,
532 formatter: emit::template::Formatter,
533 ) -> fmt::Result {
534 use sval::Value as _;
535
536 match value.tag() {
537 Some(sval::tags::NUMBER) => self.write(formatter.apply(value), NUMBER),
538 _ => self.write(formatter.apply(value), TEXT),
539 }
540
541 Ok(())
542 }
543}
544
545const KIND: Color = Color::Ansi256(174);
546const MDL_FIRST: Color = Color::Ansi256(248);
547const MDL_LAST: Color = Color::Ansi256(244);
548
549const TEXT: Color = Color::Ansi256(69);
550const NUMBER: Color = Color::Ansi256(135);
551const ATOM: Color = Color::Ansi256(168);
552const IDENT: Color = Color::Ansi256(170);
553const FIELD: Color = Color::Ansi256(174);
554
555fn trace_id_color(trace_id: &emit::TraceId) -> u8 {
556 let mut hash = 0;
557
558 for b in trace_id.to_u128().to_le_bytes() {
559 hash ^= b;
560 }
561
562 hash
563}
564
565fn span_id_color(span_id: &emit::SpanId) -> u8 {
566 let mut hash = 0;
567
568 for b in span_id.to_u64().to_le_bytes() {
569 hash ^= b;
570 }
571
572 hash
573}
574
575fn level_color(level: &emit::Level) -> Option<u8> {
576 match level {
577 emit::Level::Debug => Some(244),
578 emit::Level::Info => None,
579 emit::Level::Warn => Some(202),
580 emit::Level::Error => Some(124),
581 }
582}
583
584fn write_fg(buf: &mut Buffer, v: impl fmt::Display, color: Color) {
585 let _ = buf.set_color(ColorSpec::new().set_fg(Some(color)));
586 let _ = write!(buf, "{}", v);
587 let _ = buf.reset();
588}
589
590fn try_write_fg(buf: &mut Buffer, v: impl fmt::Display, color: Option<Color>) {
591 if let Some(color) = color {
592 write_fg(buf, v, color);
593 } else {
594 write_plain(buf, v);
595 }
596}
597
598fn write_plain(buf: &mut Buffer, v: impl fmt::Display) {
599 let _ = write!(buf, "{}", v);
600}
601
602impl<'a> TokenWriter<'a> {
603 fn write(&mut self, v: impl fmt::Display, color: Color) {
604 write_fg(&mut *self.buf, v, color);
605 }
606}
607
608fn with_shared_buf(writer: &BufferWriter, with_buf: impl FnOnce(&BufferWriter, &mut Buffer)) {
609 thread_local! {
610 static STDOUT: RefCell<Option<Buffer>> = RefCell::new(None);
611 }
612
613 STDOUT.with(|buf| {
614 match buf.try_borrow_mut() {
615 Ok(mut slot) => {
617 match &mut *slot {
618 Some(buf) => {
620 buf.clear();
621 with_buf(&writer, buf);
622 }
623 None => {
626 let mut buf = writer.buffer();
627 with_buf(&writer, &mut buf);
628
629 *slot = Some(buf);
630 }
631 }
632 }
633 Err(_) => {
636 with_buf(&writer, &mut writer.buffer());
637 }
638 }
639 });
640}
641
642#[cfg(test)]
643mod tests {
644 use super::*;
645
646 use std::str;
647
648 #[test]
649 fn write_log() {
650 let mut buf = Buffer::no_color();
651
652 write_event(
653 &mut buf,
654 emit::evt!(
655 extent: emit::Timestamp::try_from_str("2024-01-01T01:02:03.000Z").unwrap(),
656 "Hello, {user}",
657 user: "Rust",
658 extra: true,
659 ),
660 );
661
662 assert_eq!(
663 "2024-01-01T01:02:03Z emit_term tests Hello, Rust\n",
664 str::from_utf8(buf.as_slice()).unwrap()
665 );
666 }
667
668 #[test]
669 fn write_log_err() {
670 let mut buf = Buffer::no_color();
671
672 write_event(
673 &mut buf,
674 emit::evt!(
675 extent: emit::Timestamp::try_from_str("2024-01-01T01:02:03.000Z").unwrap(),
676 "An error",
677 lvl: "error",
678 err: std::io::Error::new(std::io::ErrorKind::Other, "Something went wrong"),
679 ),
680 );
681
682 assert_eq!(
683 "2024-01-01T01:02:03Z error emit_term tests An error\n err: Something went wrong\n",
684 str::from_utf8(buf.as_slice()).unwrap()
685 );
686 }
687
688 #[test]
689 fn write_span() {
690 let mut buf = Buffer::no_color();
691
692 write_event(
693 &mut buf,
694 emit::evt!(
695 extent:
696 emit::Timestamp::try_from_str("2024-01-01T01:02:03.000Z").unwrap()..
697 emit::Timestamp::try_from_str("2024-01-01T01:02:04.000Z").unwrap(),
698 "Hello, {user}",
699 user: "Rust",
700 evt_kind: "span",
701 trace_id: "4bf92f3577b34da6a3ce929d0e0e4736",
702 span_id: "00f067aa0ba902b7",
703 extra: true,
704 ),
705 );
706
707 assert_eq!(
708 "▓ 4bf92f ▓ 00f0 2024-01-01T01:02:04Z 1000ms span emit_term tests Hello, Rust\n",
709 str::from_utf8(buf.as_slice()).unwrap()
710 );
711 }
712
713 #[test]
714 fn write_metric() {
715 let mut buf = Buffer::no_color();
716
717 write_event(
718 &mut buf,
719 emit::evt!(
720 extent: emit::Timestamp::try_from_str("2024-01-01T01:02:03.000Z").unwrap(),
721 "{metric_agg} of {metric_name} is {metric_value}",
722 user: "Rust",
723 evt_kind: "metric",
724 metric_name: "test",
725 metric_agg: "count",
726 metric_value: 42,
727 ),
728 );
729
730 assert_eq!(
731 "2024-01-01T01:02:03Z metric emit_term tests count of test is 42\n",
732 str::from_utf8(buf.as_slice()).unwrap()
733 );
734 }
735
736 #[test]
737 fn write_metric_timeseries() {
738 let mut buf = Buffer::no_color();
739
740 write_event(
741 &mut buf,
742 emit::evt!(
743 extent:
744 emit::Timestamp::try_from_str("2024-01-01T01:02:00.000Z").unwrap()..
745 emit::Timestamp::try_from_str("2024-01-01T01:02:10.000Z").unwrap(),
746 "{metric_agg} of {metric_name} is {metric_value}",
747 user: "Rust",
748 evt_kind: "metric",
749 metric_name: "test",
750 metric_agg: "count",
751 #[emit::as_value]
752 metric_value: [
753 0,
754 1,
755 2,
756 3,
757 4,
758 5,
759 1,
760 2,
761 3,
762 4,
763 5,
764 ],
765 ),
766 );
767
768 assert_eq!("2024-01-01T01:02:10Z 10s metric emit_term tests count of test is [0, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]\n▁▃▄▅▆▇▃▄▅▆▇\n", str::from_utf8(buf.as_slice()).unwrap());
769 }
770}