rm_lisa/display/
tracing.rs

1//! Integrate a [`crate::display::SuperConsole`] into the [`tracing`] crate.
2
3use crate::display::{SuperConsole, renderers::ConsoleOutputFeatures};
4use chrono::prelude::*;
5use fnv::FnvHashMap;
6use serde_json::{Value as JSONValue, json};
7use std::{
8	error::Error as StdError,
9	fmt::{Debug, Display, Formatter, Result as FmtResult},
10	hash::BuildHasherDefault,
11	io::Write as IoWrite,
12	sync::Arc,
13};
14use tokio::runtime::Handle;
15use tracing::{
16	Event, Level, Metadata, Subscriber,
17	field::{Field, Visit},
18	span::{Attributes as SpanAttributes, Id as SpanId},
19	subscriber::Interest,
20};
21use tracing_subscriber::{Layer, layer::Context, registry::LookupSpan};
22use valuable::{
23	Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit as ValuableVisit,
24};
25use valuable_serde::Serializable;
26
27/// A connector that connects hooks up [`tracing`] to a specific [`SuperConsole`].
28pub struct TracableSuperConsole<
29	StdoutTy: IoWrite + ConsoleOutputFeatures + Send + 'static,
30	StderrTy: IoWrite + ConsoleOutputFeatures + Send + 'static,
31> {
32	/// The underlying super console to log too.
33	console: Arc<SuperConsole<StdoutTy, StderrTy>>,
34}
35
36impl<
37	StdoutTy: IoWrite + ConsoleOutputFeatures + Send + 'static,
38	StderrTy: IoWrite + ConsoleOutputFeatures + Send + 'static,
39> TracableSuperConsole<StdoutTy, StderrTy>
40{
41	#[must_use]
42	pub fn new(console: Arc<SuperConsole<StdoutTy, StderrTy>>) -> Self {
43		Self { console }
44	}
45}
46
47impl<
48	StdoutTy: IoWrite + ConsoleOutputFeatures + Send + 'static,
49	StderrTy: IoWrite + ConsoleOutputFeatures + Send + 'static,
50	SubscriberTy,
51> Layer<SubscriberTy> for TracableSuperConsole<StdoutTy, StderrTy>
52where
53	SubscriberTy: Subscriber + for<'lookup> LookupSpan<'lookup>,
54{
55	fn register_callsite(&self, _metadata: &'static Metadata<'static>) -> Interest {
56		Interest::always()
57	}
58
59	fn on_new_span(&self, attrs: &SpanAttributes<'_>, id: &SpanId, ctx: Context<'_, SubscriberTy>) {
60		let mut span_data = SuperConsoleSpanData::new();
61		span_data.reserve(attrs.fields().len());
62		attrs.record(&mut span_data);
63
64		if let Some(span) = ctx.span(id) {
65			span.extensions_mut().insert(span_data);
66		}
67	}
68
69	fn on_event(&self, event: &Event<'_>, ctxt: Context<'_, SubscriberTy>) {
70		let mut visitor = SuperConsoleLogMessage::new();
71
72		let mut optional_span = ctxt.event_span(event);
73		while let Some(current_span) = optional_span {
74			if let Some(ext) = current_span.extensions().get::<SuperConsoleSpanData>() {
75				visitor.visit_span(ext);
76			}
77			optional_span = current_span.parent();
78		}
79
80		visitor.set_level(*event.metadata().level());
81		visitor.reserve_metadata_space(event.metadata().fields().len());
82		event.record(&mut visitor);
83		// Are we in a tokio runtime? if so we need to block in place.
84		if let Ok(h) = Handle::try_current() {
85			tokio::task::block_in_place(move || {
86				h.block_on(async { self.console.log(visitor).await });
87			});
88		} else {
89			_ = self.console.log_sync(visitor);
90		}
91	}
92}
93
94/// A structure that represents a "log message".
95///
96/// This is implemented through a tracing visitor that visits multiple tracing
97/// [`tracing_subscriber::registry::SpanRef`]'s, and finally one
98/// [`tracing::Event`] to generate a final log message.
99#[allow(
100	// In this case booleans are the best way to track state through
101	// tracing.
102	clippy::struct_excessive_bools,
103)]
104#[derive(Clone, Debug, PartialEq)]
105pub struct SuperConsoleLogMessage {
106	/// The time this event message was created at.
107	at: DateTime<Utc>,
108	/// An optional color code to use when rendering a terminal.
109	color: Option<String>,
110	/// Force combine the message + fields to render without any gutter
111	/// that may exist.
112	force_combine: bool,
113	/// If we should hide the 'fields' values when rendering 'for humans'
114	/// e.g. not in JSON.
115	hide_fields_for_humans: bool,
116	/// The "ID" or unique identifier for this log message.
117	///
118	/// Not shown in color/text, but useful for JSON where you want to key on
119	/// what fields may or may not be in a message.
120	id: Option<String>,
121	/// The level at which the log message was produced.
122	level: Level,
123	/// The "name" of the subsystem that created this log message.
124	subsystem: Option<String>,
125	/// A way to turn off "decorations" for the color printing.
126	should_decorate: bool,
127	/// If we should target STDOUT, this is false by default and will head to STDERR.
128	target_stdout: bool,
129	/// The underlying message.
130	message: Option<String>,
131	/// Extra bits of metadata to render.
132	metadata: FnvHashMap<&'static str, FlattenedTracingField>,
133}
134
135impl SuperConsoleLogMessage {
136	/// Create a new visitor that is capable of visiting events.
137	#[must_use]
138	pub(self) fn new() -> Self {
139		Self {
140			at: Utc::now(),
141			color: None,
142			id: None,
143			force_combine: false,
144			hide_fields_for_humans: false,
145			level: Level::INFO,
146			subsystem: None,
147			should_decorate: true,
148			target_stdout: false,
149			message: None,
150			metadata: FnvHashMap::with_capacity_and_hasher(0, BuildHasherDefault::default()),
151		}
152	}
153
154	/// Reserve an addtional N amount of space. Used to save on re-allocs.
155	pub(self) fn reserve_metadata_space(&mut self, additional: usize) {
156		self.metadata.reserve(additional);
157	}
158
159	/// Get the time this log message occured at.
160	#[must_use]
161	pub const fn at(&self) -> &DateTime<Utc> {
162		&self.at
163	}
164
165	/// Get a color to use when rendering on the terminal, if the logger
166	/// chooses to use this.
167	#[must_use]
168	pub fn color(&self) -> Option<&str> {
169		self.color.as_deref()
170	}
171
172	/// Check if we should hide fields "for humans", e.g. not in JSON.
173	#[must_use]
174	pub const fn should_hide_fields_for_humans(&self) -> bool {
175		self.hide_fields_for_humans
176	}
177
178	/// The "ID" or unique identifier for this log message.
179	///
180	/// Not shown in color/text, but useful for JSON where you want to key on
181	/// what fields may or may not be in a message.
182	#[must_use]
183	pub fn id(&self) -> Option<&str> {
184		self.id.as_deref()
185	}
186
187	/// If we should force combine fields, and messages for this log message.
188	#[must_use]
189	pub const fn force_combine(&self) -> bool {
190		self.force_combine
191	}
192
193	/// The "name" of the subsystem that created this log message.
194	#[must_use]
195	pub fn subsytem(&self) -> Option<&str> {
196		self.subsystem.as_deref()
197	}
198
199	/// The message or 'log line'.
200	#[must_use]
201	pub fn message(&self) -> Option<&str> {
202		self.message.as_deref()
203	}
204
205	/// Get the log level this message was produced at.
206	#[must_use]
207	pub const fn level(&self) -> &Level {
208		&self.level
209	}
210
211	#[must_use]
212	pub const fn should_decorate(&self) -> bool {
213		self.should_decorate
214	}
215
216	#[must_use]
217	pub const fn towards_stdout(&self) -> bool {
218		self.target_stdout
219	}
220
221	/// Any extra set of fields folks have attached.
222	///
223	/// NOTE: structured fields here through valuable will be rendered as
224	/// their keys being: "key.innerkey"="value" here.
225	#[must_use]
226	pub const fn metadata(&self) -> &FnvHashMap<&'static str, FlattenedTracingField> {
227		&self.metadata
228	}
229
230	/// Set the log level for this even.
231	pub(self) fn set_level(&mut self, level: Level) {
232		self.level = level;
233	}
234
235	/// Visit a span data and append all information from the spans into
236	/// our context.
237	///
238	/// This expects to be called repeatedly (one for each span), and should be
239	/// visited in the order of current span first, followed by it's parent, etc.
240	pub(self) fn visit_span(&mut self, ext: &SuperConsoleSpanData) {
241		for (key, value) in ext.fields() {
242			match *key {
243				"id" => {
244					if self.id.is_none() {
245						_ = self.id.insert(Self::get_field_as_string(value));
246					}
247				}
248				"lisa.force_combine_fields" => {
249					self.force_combine = Self::get_field_as_bool(value);
250				}
251				"lisa.hide_fields_for_humans" => {
252					self.hide_fields_for_humans = Self::get_field_as_bool(value);
253				}
254				"lisa.subsystem" => {
255					if self.subsystem.is_none() {
256						_ = self.subsystem.insert(Self::get_field_as_string(value));
257					}
258				}
259				"lisa.stdout" => {
260					self.target_stdout = Self::get_field_as_bool(value);
261				}
262				"lisa.decorate" => {
263					self.should_decorate = Self::get_field_as_bool(value);
264				}
265				key_value => {
266					if !self.metadata.contains_key(key_value) {
267						self.metadata.insert(key_value, value.clone());
268					}
269				}
270			}
271		}
272	}
273
274	/// Get a field as a boolean.
275	fn get_field_as_bool(value: &FlattenedTracingField) -> bool {
276		match value {
277			FlattenedTracingField::Null => false,
278			FlattenedTracingField::Boolean(val) => *val,
279			FlattenedTracingField::Bytes(val) => !val.is_empty() && val[0] > 0,
280			FlattenedTracingField::Float(val) => *val > 0.0,
281			FlattenedTracingField::Int(val) => *val > 0,
282			FlattenedTracingField::IntLarge(val) => *val > 0,
283			FlattenedTracingField::Str(val) => val.eq_ignore_ascii_case("true") || val == "1",
284			FlattenedTracingField::UnsignedInt(val) => *val > 0,
285			FlattenedTracingField::UnsignedIntLarge(val) => *val > 0,
286			FlattenedTracingField::List(val) => !val.is_empty(),
287			FlattenedTracingField::Object(val) => !val.is_empty(),
288		}
289	}
290
291	/// Get a field as an owned string.
292	fn get_field_as_string(value: &FlattenedTracingField) -> String {
293		match value {
294			FlattenedTracingField::Null => String::with_capacity(0),
295			FlattenedTracingField::Boolean(val) => format!("{val}"),
296			FlattenedTracingField::Float(val) => format!("{val}"),
297			FlattenedTracingField::Int(val) => format!("{val}"),
298			FlattenedTracingField::IntLarge(val) => format!("{val}"),
299			FlattenedTracingField::UnsignedInt(val) => format!("{val}"),
300			FlattenedTracingField::UnsignedIntLarge(val) => format!("{val}"),
301			FlattenedTracingField::Str(val) => val.clone(),
302			FlattenedTracingField::Bytes(val) => format!("{val:02x?}"),
303			FlattenedTracingField::List(_) | FlattenedTracingField::Object(_) => {
304				format!("{value}")
305			}
306		}
307	}
308
309	fn do_field_visit(&mut self, field: &Field, value: FlattenedTracingField) {
310		match field.name() {
311			"id" => {
312				_ = self.id.insert(Self::get_field_as_string(&value));
313			}
314			"lisa.subsystem" => {
315				_ = self.subsystem.insert(Self::get_field_as_string(&value));
316			}
317			"lisa.stdout" => {
318				self.target_stdout = Self::get_field_as_bool(&value);
319			}
320			"lisa.decorate" => {
321				self.should_decorate = Self::get_field_as_bool(&value);
322			}
323			"lisa.force_combine_fields" => {
324				self.force_combine = Self::get_field_as_bool(&value);
325			}
326			"lisa.hide_fields_for_humans" => {
327				self.hide_fields_for_humans = Self::get_field_as_bool(&value);
328			}
329			"message" => {
330				_ = self.message.insert(Self::get_field_as_string(&value));
331			}
332			key_value => {
333				self.metadata.insert(key_value, value);
334			}
335		}
336	}
337}
338
339impl Visit for SuperConsoleLogMessage {
340	fn record_f64(&mut self, field: &Field, value: f64) {
341		self.do_field_visit(field, FlattenedTracingField::Float(value));
342	}
343
344	fn record_i64(&mut self, field: &Field, value: i64) {
345		self.do_field_visit(field, FlattenedTracingField::Int(value));
346	}
347
348	fn record_u64(&mut self, field: &Field, value: u64) {
349		self.do_field_visit(field, FlattenedTracingField::UnsignedInt(value));
350	}
351
352	fn record_i128(&mut self, field: &Field, value: i128) {
353		self.do_field_visit(field, FlattenedTracingField::IntLarge(value));
354	}
355
356	fn record_u128(&mut self, field: &Field, value: u128) {
357		self.do_field_visit(field, FlattenedTracingField::UnsignedIntLarge(value));
358	}
359
360	fn record_bool(&mut self, field: &Field, value: bool) {
361		self.do_field_visit(field, FlattenedTracingField::Boolean(value));
362	}
363
364	fn record_str(&mut self, field: &Field, value: &str) {
365		self.do_field_visit(field, FlattenedTracingField::Str(value.to_owned()));
366	}
367
368	fn record_bytes(&mut self, field: &Field, value: &[u8]) {
369		self.do_field_visit(field, FlattenedTracingField::Bytes(Vec::from(value)));
370	}
371
372	fn record_error(&mut self, field: &Field, value: &(dyn StdError + 'static)) {
373		self.do_field_visit(field, FlattenedTracingField::Str(format!("{value}")));
374	}
375
376	fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
377		self.do_field_visit(field, FlattenedTracingField::Str(format!("{value:?}")));
378	}
379
380	fn record_value(&mut self, field: &Field, value: Value<'_>) {
381		self.do_field_visit(field, valuable_to_flattened(value));
382	}
383}
384
385const SUPER_CONSOLE_LOG_MESSAGE_FIELDS: &[NamedField<'static>] = &[
386	NamedField::new("id"),
387	NamedField::new("level"),
388	NamedField::new("subsystem"),
389	NamedField::new("should_decorate"),
390	NamedField::new("target_stdout"),
391	NamedField::new("metadata"),
392];
393
394impl Structable for SuperConsoleLogMessage {
395	fn definition(&self) -> StructDef<'_> {
396		StructDef::new_static(
397			"SuperConsoleLogMessage",
398			Fields::Named(SUPER_CONSOLE_LOG_MESSAGE_FIELDS),
399		)
400	}
401}
402
403impl Valuable for SuperConsoleLogMessage {
404	fn as_value(&self) -> Value<'_> {
405		Value::Structable(self)
406	}
407
408	fn visit(&self, visitor: &mut dyn ValuableVisit) {
409		visitor.visit_named_fields(&NamedValues::new(
410			SUPER_CONSOLE_LOG_MESSAGE_FIELDS,
411			&[
412				Valuable::as_value(&self.id),
413				Valuable::as_value(&format!("{}", self.level())),
414				Valuable::as_value(&self.subsystem),
415				Valuable::as_value(&self.should_decorate),
416				Valuable::as_value(&self.target_stdout),
417				Valuable::as_value(&self.metadata),
418			],
419		));
420	}
421}
422
423/// A field that gets stored on a tracing span, or event.
424///
425/// We keep track of tracing fields in this structured format. Specifically
426/// all fields are stored 'flattened' so `{event: {key: 10}}` gets stored as a
427/// single key/value: `event.key=10`.
428///
429/// They are stored to match 1:1 with the tracing types that generate them, so
430/// it's easy to format in various types of serialization formats. The only two
431/// formats that don't get a specific value are:
432///
433/// - `Error`: because of lifetime issues we will render it as it's Display trait.
434/// - `Debug`: because of limetime issues we will render it as it's Debug trait.
435#[derive(Clone, PartialEq, Valuable)]
436pub enum FlattenedTracingField {
437	Null,
438	Float(f64),
439	Int(i64),
440	UnsignedInt(u64),
441	IntLarge(i128),
442	UnsignedIntLarge(u128),
443	Boolean(bool),
444	Str(String),
445	Bytes(Vec<u8>),
446	List(Vec<FlattenedTracingField>),
447	Object(FnvHashMap<String, FlattenedTracingField>),
448}
449
450impl FlattenedTracingField {
451	/// Get the total count of inner fields that will be rendered.
452	#[must_use]
453	pub fn field_count(&self) -> usize {
454		match self {
455			FlattenedTracingField::Boolean(_)
456			| FlattenedTracingField::Bytes(_)
457			| FlattenedTracingField::Float(_)
458			| FlattenedTracingField::Int(_)
459			| FlattenedTracingField::IntLarge(_)
460			| FlattenedTracingField::Null
461			| FlattenedTracingField::Str(_)
462			| FlattenedTracingField::UnsignedInt(_)
463			| FlattenedTracingField::UnsignedIntLarge(_) => 1,
464			FlattenedTracingField::List(list) => {
465				list.iter().map(FlattenedTracingField::field_count).sum()
466			}
467			FlattenedTracingField::Object(obj) => {
468				obj.values().map(FlattenedTracingField::field_count).sum()
469			}
470		}
471	}
472}
473
474impl Display for FlattenedTracingField {
475	fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
476		match self {
477			Self::Null => write!(fmt, ""),
478			Self::Boolean(value) => write!(fmt, "{value}"),
479			Self::Bytes(value) => write!(fmt, "{value:02x?}"),
480			Self::Float(value) => write!(fmt, "{value}"),
481			Self::Int(value) => write!(fmt, "{value}"),
482			Self::IntLarge(value) => write!(fmt, "{value}"),
483			Self::Str(value) => write!(fmt, "{value}"),
484			Self::UnsignedInt(value) => write!(fmt, "{value}"),
485			Self::UnsignedIntLarge(value) => write!(fmt, "{value}"),
486			Self::List(value) => {
487				for (idx, val) in value.iter().enumerate() {
488					if idx != 0 {
489						write!(fmt, ",")?;
490					}
491					write!(fmt, "{val}")?;
492				}
493				Ok(())
494			}
495			Self::Object(value) => {
496				write!(fmt, "{{")?;
497				let mut written_once = false;
498				for (key, val) in value {
499					if written_once {
500						write!(fmt, ",")?;
501					}
502					written_once = true;
503					write!(fmt, "{key}={val}")?;
504				}
505				write!(fmt, "}}")
506			}
507		}
508	}
509}
510
511impl Debug for FlattenedTracingField {
512	fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
513		match self {
514			Self::Null => write!(fmt, ""),
515			Self::Boolean(value) => write!(fmt, "{value:?}"),
516			Self::Bytes(value) => write!(fmt, "{value:02x?}"),
517			Self::Float(value) => write!(fmt, "{value:?}"),
518			Self::Int(value) => write!(fmt, "{value:?}"),
519			Self::IntLarge(value) => write!(fmt, "{value:?}"),
520			Self::Str(value) => write!(fmt, "{value:?}"),
521			Self::UnsignedInt(value) => write!(fmt, "{value:?}"),
522			Self::UnsignedIntLarge(value) => write!(fmt, "{value:?}"),
523			Self::List(value) => {
524				let mut has_written = false;
525				for val in value {
526					if has_written {
527						write!(fmt, ",")?;
528					} else {
529						has_written = true;
530					}
531					write!(fmt, "{val:?}")?;
532				}
533				Ok(())
534			}
535			Self::Object(value) => {
536				write!(fmt, "{{")?;
537				let mut written_once = false;
538				for (key, val) in value {
539					if written_once {
540						write!(fmt, ",")?;
541					}
542					written_once = true;
543					write!(fmt, "{key}={val:?}")?;
544				}
545				write!(fmt, "}}")
546			}
547		}
548	}
549}
550
551/// [`tracing_subscriber::registry::SpanRef`]'s don't store the actual values.
552///
553/// This is to support cases like embedded where keeping the span data is too
554/// expensive. Luckily for us! Lisa doesn't need to support embedded data, and
555/// won't have too!
556///
557/// This means we can just keep a map of the fields around in memory.
558#[derive(Clone, Debug, PartialEq, Valuable)]
559struct SuperConsoleSpanData {
560	/// Fields that are recorded as part of this span.
561	fields: FnvHashMap<&'static str, FlattenedTracingField>,
562}
563
564impl SuperConsoleSpanData {
565	#[must_use]
566	pub fn new() -> Self {
567		Self {
568			fields: FnvHashMap::with_capacity_and_hasher(0, BuildHasherDefault::default()),
569		}
570	}
571
572	/// Reserve an addtional N amount of space. Used to save on re-allocs.
573	pub fn reserve(&mut self, additional: usize) {
574		self.fields.reserve(additional);
575	}
576
577	#[must_use]
578	pub fn fields(&self) -> &FnvHashMap<&'static str, FlattenedTracingField> {
579		&self.fields
580	}
581}
582
583impl Visit for SuperConsoleSpanData {
584	fn record_f64(&mut self, field: &Field, value: f64) {
585		self.fields
586			.insert(field.name(), FlattenedTracingField::Float(value));
587	}
588
589	fn record_i64(&mut self, field: &Field, value: i64) {
590		self.fields
591			.insert(field.name(), FlattenedTracingField::Int(value));
592	}
593
594	fn record_u64(&mut self, field: &Field, value: u64) {
595		self.fields
596			.insert(field.name(), FlattenedTracingField::UnsignedInt(value));
597	}
598
599	fn record_i128(&mut self, field: &Field, value: i128) {
600		self.fields
601			.insert(field.name(), FlattenedTracingField::IntLarge(value));
602	}
603
604	fn record_u128(&mut self, field: &Field, value: u128) {
605		self.fields
606			.insert(field.name(), FlattenedTracingField::UnsignedIntLarge(value));
607	}
608
609	fn record_bool(&mut self, field: &Field, value: bool) {
610		self.fields
611			.insert(field.name(), FlattenedTracingField::Boolean(value));
612	}
613
614	fn record_str(&mut self, field: &Field, value: &str) {
615		self.fields
616			.insert(field.name(), FlattenedTracingField::Str(value.to_owned()));
617	}
618
619	fn record_bytes(&mut self, field: &Field, value: &[u8]) {
620		self.fields
621			.insert(field.name(), FlattenedTracingField::Bytes(Vec::from(value)));
622	}
623
624	fn record_error(&mut self, field: &Field, value: &(dyn StdError + 'static)) {
625		self.fields
626			.insert(field.name(), FlattenedTracingField::Str(format!("{value}")));
627	}
628
629	fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
630		self.fields.insert(
631			field.name(),
632			FlattenedTracingField::Str(format!("{value:?}")),
633		);
634	}
635
636	fn record_value(&mut self, field: &Field, value: Value<'_>) {
637		self.fields
638			.insert(field.name(), valuable_to_flattened(value));
639	}
640}
641
642/// Convert a valuable type into a Flattened tracing field.
643///
644/// This backs off of [`valuable_serde::Serializable`] to provide the actual
645/// implementation of converting to a serde set of types. Then we take those
646/// serde types and turn them into a tracing field.
647#[must_use]
648fn valuable_to_flattened(value: Value<'_>) -> FlattenedTracingField {
649	json_value_to_flattened(json!(Serializable::new(value)))
650}
651
652/// Convert a json value into a Flattened tracing field.
653#[must_use]
654fn json_value_to_flattened(as_json_value: JSONValue) -> FlattenedTracingField {
655	match as_json_value {
656		JSONValue::Null => FlattenedTracingField::Null,
657		JSONValue::Bool(val) => FlattenedTracingField::Boolean(val),
658		JSONValue::Number(val) => {
659			if let Some(float) = val.as_f64() {
660				FlattenedTracingField::Float(float)
661			} else if let Some(uint) = val.as_u64() {
662				FlattenedTracingField::UnsignedInt(uint)
663			} else if let Some(int) = val.as_i64() {
664				FlattenedTracingField::Int(int)
665			} else if let Some(uint) = val.as_u128() {
666				FlattenedTracingField::UnsignedIntLarge(uint)
667			} else if let Some(int) = val.as_i128() {
668				FlattenedTracingField::IntLarge(int)
669			} else {
670				FlattenedTracingField::Null
671			}
672		}
673		JSONValue::String(val) => FlattenedTracingField::Str(val.clone()),
674		JSONValue::Array(val) => {
675			let mut list = Vec::with_capacity(val.len());
676			for item in val {
677				list.push(json_value_to_flattened(item));
678			}
679			FlattenedTracingField::List(list)
680		}
681		JSONValue::Object(val) => {
682			let mut obj =
683				FnvHashMap::with_capacity_and_hasher(val.len(), BuildHasherDefault::default());
684			for (key, value) in val {
685				obj.insert(key, json_value_to_flattened(value));
686			}
687			FlattenedTracingField::Object(obj)
688		}
689	}
690}
691
692#[cfg(test)]
693mod unit_tests {
694	use super::*;
695	use serde_json::{Value as JSONValue, json};
696
697	#[test]
698	pub fn super_console_span_data_reservation() {
699		let mut span = SuperConsoleSpanData::new();
700		assert_eq!(
701			span.fields().capacity(),
702			0,
703			"SuperConsoleSpanData new should start with a 0 capacity item!",
704		);
705		span.reserve(10);
706		assert!(
707			span.fields().capacity() >= 10,
708			"SuperConsoleSpanData did not our first 10 items: {}",
709			span.fields.capacity(),
710		);
711	}
712
713	#[test]
714	pub fn valuable_to_flattened_simple_conversion() {
715		#[derive(Valuable)]
716		struct InnerObject {
717			array: Vec<u64>,
718		}
719		#[derive(Valuable)]
720		struct SimpleObject {
721			null: Option<bool>,
722			bool: bool,
723			real: u64,
724			float: f64,
725			str: String,
726			array: Vec<String>,
727			inner_obj: InnerObject,
728		}
729
730		let serializable = SimpleObject {
731			null: None,
732			bool: true,
733			real: 10,
734			float: 10.5,
735			str: "hello".to_owned(),
736			array: vec!["oh".to_owned(), "hello".to_owned(), "there".to_owned()],
737			inner_obj: InnerObject {
738				array: vec![10, 11, 12],
739			},
740		};
741
742		let mut global_object = FnvHashMap::default();
743		global_object.insert("null".to_owned(), FlattenedTracingField::Null);
744		global_object.insert("bool".to_owned(), FlattenedTracingField::Boolean(true));
745		global_object.insert("real".to_owned(), FlattenedTracingField::Float(10.0));
746		global_object.insert("float".to_owned(), FlattenedTracingField::Float(10.5));
747		global_object.insert(
748			"str".to_owned(),
749			FlattenedTracingField::Str("hello".to_owned()),
750		);
751		global_object.insert(
752			"array".to_owned(),
753			FlattenedTracingField::List(vec![
754				FlattenedTracingField::Str("oh".to_owned()),
755				FlattenedTracingField::Str("hello".to_owned()),
756				FlattenedTracingField::Str("there".to_owned()),
757			]),
758		);
759
760		let mut inner_object = FnvHashMap::default();
761		inner_object.insert(
762			"array".to_owned(),
763			FlattenedTracingField::List(vec![
764				FlattenedTracingField::Float(10.0),
765				FlattenedTracingField::Float(11.0),
766				FlattenedTracingField::Float(12.0),
767			]),
768		);
769		global_object.insert(
770			"inner_obj".to_owned(),
771			FlattenedTracingField::Object(inner_object),
772		);
773
774		assert_eq!(
775			valuable_to_flattened(serializable.as_value()),
776			FlattenedTracingField::Object(global_object),
777			"Valuable to flattened differed: {:?}",
778			valuable_to_flattened(serializable.as_value()),
779		);
780	}
781
782	/// Do a basic JSON -> FlattenedTracingField conversion.
783	///
784	/// Right now we haven't had bugs here so this is mostly just a simple check
785	/// of each type of things.
786	#[test]
787	pub fn json_to_flattened_simple_conversion() {
788		let json_nul = JSONValue::Null;
789		let json_bool = json!(true);
790		let json_real = json!(100);
791		let json_flot = json!(10.5);
792		let json_strg = json!("hello world!");
793		let json_arry = json!(["oh no", "why did", "we do this"]);
794		let json_objc = json!({
795			"a": false,
796			"b": 10,
797			"c": 10.2,
798			"d": "hello world",
799			"e": ["a", "b", "c"],
800			"f": {
801				"g": [10, 11, 12],
802			},
803		});
804
805		assert_eq!(
806			json_value_to_flattened(json_nul.clone()),
807			FlattenedTracingField::Null,
808			"Converting JSON NULL did not produce FTF Null: {}",
809			json_value_to_flattened(json_nul),
810		);
811		assert_eq!(
812			json_value_to_flattened(json_bool.clone()),
813			FlattenedTracingField::Boolean(true),
814			"Converting JSON BOOL did not produce FTF Bool: {}",
815			json_value_to_flattened(json_bool),
816		);
817		assert_eq!(
818			json_value_to_flattened(json_real.clone()),
819			FlattenedTracingField::Float(100.0),
820			"Converting JSON REAL did not produce FTF UnsignedInt: {}",
821			json_value_to_flattened(json_real),
822		);
823		assert_eq!(
824			json_value_to_flattened(json_flot.clone()),
825			FlattenedTracingField::Float(10.5),
826			"Converting JSON Float did not produce FTF Float: {}",
827			json_value_to_flattened(json_flot),
828		);
829		assert_eq!(
830			json_value_to_flattened(json_strg.clone()),
831			FlattenedTracingField::Str("hello world!".to_owned()),
832			"Converting JSON String did not produce FTF String: {}",
833			json_value_to_flattened(json_strg),
834		);
835		assert_eq!(
836			json_value_to_flattened(json_arry.clone()),
837			FlattenedTracingField::List(vec![
838				FlattenedTracingField::Str("oh no".to_owned()),
839				FlattenedTracingField::Str("why did".to_owned()),
840				FlattenedTracingField::Str("we do this".to_owned()),
841			]),
842			"Converting JSON List did not produce FTF List: {}",
843			json_value_to_flattened(json_arry),
844		);
845
846		let mut f_object = FnvHashMap::default();
847		f_object.insert(
848			"g".to_owned(),
849			FlattenedTracingField::List(vec![
850				FlattenedTracingField::Float(10.0),
851				FlattenedTracingField::Float(11.0),
852				FlattenedTracingField::Float(12.0),
853			]),
854		);
855		let mut global_object = FnvHashMap::default();
856		global_object.insert("a".to_owned(), FlattenedTracingField::Boolean(false));
857		global_object.insert("b".to_owned(), FlattenedTracingField::Float(10.0));
858		global_object.insert("c".to_owned(), FlattenedTracingField::Float(10.2));
859		global_object.insert(
860			"d".to_owned(),
861			FlattenedTracingField::Str("hello world".to_owned()),
862		);
863		global_object.insert(
864			"e".to_owned(),
865			FlattenedTracingField::List(vec![
866				FlattenedTracingField::Str("a".to_owned()),
867				FlattenedTracingField::Str("b".to_owned()),
868				FlattenedTracingField::Str("c".to_owned()),
869			]),
870		);
871		global_object.insert("f".to_owned(), FlattenedTracingField::Object(f_object));
872		assert_eq!(
873			json_value_to_flattened(json_objc.clone()),
874			FlattenedTracingField::Object(global_object),
875			"Converting JSON List did not produce FTF Object: {}",
876			json_value_to_flattened(json_objc),
877		);
878	}
879}