1use chrono::Utc;
78use ser::ECSLogLine;
79use ser::ECSSpanEvent;
80use ser::LogFile;
81use ser::LogOrigin;
82use serde::Serialize;
83use serde_json::Map;
84use serde_json::Value;
85use std::borrow::Cow;
86use std::collections::HashMap;
87use std::io;
88use std::io::sink;
89use std::io::Stderr;
90use std::io::Stdout;
91use std::io::Write;
92use std::sync::Mutex;
93use tracing_core::dispatcher::SetGlobalDefaultError;
94use tracing_core::span::Attributes;
95use tracing_core::span::Id;
96use tracing_core::span::Record;
97use tracing_core::Event;
98use tracing_core::Subscriber;
99use tracing_log::log_tracer::SetLoggerError;
100use tracing_log::LogTracer;
101use tracing_subscriber::fmt::format::FmtSpan;
102use tracing_subscriber::fmt::MakeWriter;
103use tracing_subscriber::fmt::SubscriberBuilder;
104use tracing_subscriber::layer::Context;
105use tracing_subscriber::registry::LookupSpan;
106use tracing_subscriber::EnvFilter;
107use tracing_subscriber::Layer;
108
109mod attribute_mapper;
110mod ser;
111mod visitor;
112
113pub use attribute_mapper::{AttributeMapper, EVENT_SPAN_NAME};
114
115pub struct ECSLayer<W>
118where
119 W: for<'writer> MakeWriter<'writer> + 'static,
120{
121 writer: Mutex<W>,
122 attribute_mapper: Box<dyn AttributeMapper>,
123 extra_fields: serde_json::Map<String, Value>,
124 normalize_json: bool,
125 span_events: FmtSpan,
126}
127
128impl<W> ECSLayer<W>
129where
130 W: for<'writer> MakeWriter<'writer> + 'static + Send + Sync,
131{
132 pub fn install(self) -> Result<(), Error> {
144 let noout = SubscriberBuilder::default()
145 .with_writer(sink)
146 .with_env_filter(EnvFilter::from_default_env())
147 .with_span_events(FmtSpan::EXIT)
148 .finish();
149 let subscriber = self.with_subscriber(noout);
150 tracing_core::dispatcher::set_global_default(tracing_core::dispatcher::Dispatch::new(
151 subscriber,
152 ))
153 .map_err(Error::from)?;
154 LogTracer::init().map_err(Error::from)?;
155
156 Ok(())
157 }
158}
159
160impl<W> ECSLayer<W>
161where
162 W: for<'writer> MakeWriter<'writer> + 'static,
163{
164 fn log_span_event<S: Subscriber + for<'a> LookupSpan<'a>>(
165 &self,
166 id: &Id,
167 ctx: &Context<'_, S>,
168 event_action: &'static str,
169 ) {
170 let span = ctx.span(id).expect("span not found, this is a bug");
171 let span_name = span.name();
172 let span_id = id.into_u64().to_string();
173
174 let mut span_fields = HashMap::<Cow<'static, str>, Value>::new();
175 if let Some(span_object) = span.extensions().get::<HashMap<Cow<'static, str>, Value>>() {
176 span_fields.extend(span_object.clone());
177 }
178
179 let span_event = ECSSpanEvent {
180 timestamp: Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Nanos, true),
181 event_kind: "span",
182 event_action,
183 span_id,
184 span_name: span_name.to_string(),
185 dynamic_fields: self
186 .extra_fields
187 .iter()
188 .map(|(key, value)| (key.clone(), value.clone()))
189 .chain(
190 span_fields
191 .into_iter()
192 .map(|(key, value)| (key.to_string(), value)),
193 )
194 .collect(),
195 };
196
197 let writer = self.writer.lock().unwrap();
198 let mut writer = writer.make_writer();
199 let _ = if self.normalize_json {
200 serde_json::to_writer(writer.by_ref(), &span_event.normalize())
201 } else {
202 serde_json::to_writer(writer.by_ref(), &span_event)
203 };
204 let _ = writer.write(&[b'\n']);
205 }
206}
207
208impl<W, S> Layer<S> for ECSLayer<W>
209where
210 S: Subscriber + for<'a> LookupSpan<'a>,
211 W: for<'writer> MakeWriter<'writer> + 'static,
212{
213 fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
214 if self.span_events.clone() & FmtSpan::NEW == FmtSpan::NEW {
215 self.log_span_event(id, &ctx, "new");
216 }
217
218 let span = ctx.span(id).expect("span not found, this is a bug");
219
220 let mut extensions = span.extensions_mut();
221
222 if extensions.get_mut::<Map<String, Value>>().is_none() {
223 let mut object = HashMap::with_capacity(16);
224 let mut visitor = visitor::FieldVisitor::new(
225 &mut object,
226 span.name(),
227 self.attribute_mapper.as_ref(),
228 );
229 attrs.record(&mut visitor);
230 extensions.insert(object);
231 }
232 }
233
234 fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) {
235 let span = ctx.span(id).expect("span not found, this is a bug");
236 let mut extensions = span.extensions_mut();
237 if let Some(object) = extensions.get_mut::<HashMap<Cow<'static, str>, Value>>() {
238 let mut add_field_visitor =
239 visitor::FieldVisitor::new(object, span.name(), self.attribute_mapper.as_ref());
240 values.record(&mut add_field_visitor);
241 } else {
242 let mut object = HashMap::with_capacity(16);
243 let mut add_field_visitor = visitor::FieldVisitor::new(
244 &mut object,
245 span.name(),
246 self.attribute_mapper.as_ref(),
247 );
248 values.record(&mut add_field_visitor);
249 extensions.insert(object);
250 }
251 }
252
253 fn on_enter(&self, id: &Id, ctx: Context<'_, S>) {
254 if self.span_events.clone() & FmtSpan::ENTER == FmtSpan::ENTER {
255 self.log_span_event(id, &ctx, "enter");
256 }
257 }
258
259 fn on_exit(&self, id: &Id, ctx: Context<'_, S>) {
260 if self.span_events.clone() & FmtSpan::EXIT == FmtSpan::EXIT {
261 self.log_span_event(id, &ctx, "exit");
262 }
263 }
264
265 fn on_close(&self, id: Id, ctx: Context<'_, S>) {
266 if self.span_events.clone() & FmtSpan::CLOSE == FmtSpan::CLOSE {
267 self.log_span_event(&id, &ctx, "close");
268 }
269 }
270
271 fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
272 let mut span_fields = HashMap::<Cow<'static, str>, Value>::new();
273
274 let span = ctx.current_span().id().and_then(|id| {
276 ctx.span_scope(id).map(|scope| {
277 scope.from_root().fold(String::new(), |mut spans, span| {
278 if let Some(span_object) =
280 span.extensions().get::<HashMap<Cow<'static, str>, Value>>()
281 {
282 span_fields.extend(span_object.clone());
283 }
284 if !spans.is_empty() {
285 spans = format!("{}:{}", spans, span.name());
286 } else {
287 spans = span.name().to_string();
288 }
289 spans
290 })
291 })
292 });
293
294 if let Some(span) = span {
295 span_fields.insert("span.name".into(), span.into());
296 }
297
298 let metadata = event.metadata();
301 let level = metadata.level().as_str();
302 let mut target = metadata.target().to_string();
303
304 let mut fields = HashMap::with_capacity(16);
306 let mut visitor = visitor::FieldVisitor::new(
307 &mut fields,
308 EVENT_SPAN_NAME,
309 self.attribute_mapper.as_ref(),
310 );
311 event.record(&mut visitor);
312
313 let mut log_origin = LogOrigin::from(metadata);
315 if target == "log"
316 && fields.contains_key("log.target")
317 && fields.contains_key("log.module_path")
318 {
319 fields.remove("log.module_path");
320 target = value_to_string(fields.remove("log.target").unwrap()); if let (Some(file), Some(line)) = (fields.remove("log.file"), fields.remove("log.line"))
323 {
324 log_origin = LogOrigin {
325 file: LogFile {
326 line: line.as_u64().and_then(|u| u32::try_from(u).ok()),
327 name: file.as_str().map(|file| file.to_owned().into()),
328 },
329 };
330 }
331 }
332
333 let message = fields
334 .remove("message")
335 .map(value_to_string)
336 .unwrap_or_default();
337 let line = ECSLogLine {
338 timestamp: Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Nanos, true),
339 message,
340 level,
341 log_origin,
342 logger: &target,
343 dynamic_fields: self
344 .extra_fields
345 .iter()
346 .map(|(key, value)| (key.clone(), value.clone()))
347 .chain(
348 span_fields
349 .into_iter()
350 .map(|(key, value)| (key.to_string(), value)),
351 )
352 .chain(
353 fields
354 .into_iter()
355 .map(|(key, value)| (key.to_string(), value)),
356 )
357 .collect(),
358 };
359 let writer = self.writer.lock().unwrap(); let mut writer = writer.make_writer_for(metadata);
361 let _ = if self.normalize_json {
362 serde_json::to_writer(writer.by_ref(), &line.normalize())
363 } else {
364 serde_json::to_writer(writer.by_ref(), &line)
365 };
366 let _ = writer.write(&[b'\n']);
367 }
368}
369
370fn value_to_string(value: Value) -> String {
371 match value {
372 Value::String(string) => string,
373 _ => value.to_string(),
374 }
375}
376
377pub struct ECSLayerBuilder {
392 extra_fields: Option<serde_json::Map<String, Value>>,
393 attribute_mapper: Box<dyn AttributeMapper>,
394 normalize_json: bool,
395 span_events: FmtSpan,
396}
397
398impl Default for ECSLayerBuilder {
399 fn default() -> Self {
400 Self {
401 extra_fields: Default::default(),
402 attribute_mapper: Default::default(),
403 normalize_json: true,
404 span_events: FmtSpan::NONE,
405 }
406 }
407}
408
409impl Default for Box<dyn AttributeMapper> {
410 fn default() -> Self {
411 Box::new(|_span_name: &str, name: Cow<'static, str>| name)
412 }
413}
414
415impl ECSLayerBuilder {
416 pub fn with_extra_fields<F: Serialize>(mut self, extra_fields: F) -> Result<Self, Error> {
417 let as_json = serde_json::to_value(&extra_fields)
418 .map_err(|_| Error::ExtraFieldNotSerializableAsJson)?;
419 match as_json {
420 Value::Object(extra_fields) => {
421 self.extra_fields = Some(extra_fields);
422 Ok(self)
423 }
424 _ => Err(Error::ExtraFieldNotAMap),
425 }
426 }
427
428 pub fn with_attribute_mapper<M>(mut self, attribute_mapper: M) -> Self
429 where
430 M: AttributeMapper,
431 {
432 self.attribute_mapper = Box::new(attribute_mapper);
433 self
434 }
435
436 pub fn with_span_events(mut self, span_events: FmtSpan) -> Self {
474 self.span_events = span_events;
475 self
476 }
477
478 pub fn normalize_json(self, normalize_json: bool) -> Self {
500 Self {
501 normalize_json,
502 ..self
503 }
504 }
505
506 pub fn stderr(self) -> ECSLayer<fn() -> Stderr> {
507 self.build_with_writer(io::stderr)
508 }
509
510 pub fn stdout(self) -> ECSLayer<fn() -> Stdout> {
511 self.build_with_writer(io::stdout)
512 }
513
514 pub fn build_with_writer<W>(self, writer: W) -> ECSLayer<W>
515 where
516 W: for<'writer> MakeWriter<'writer> + 'static,
517 {
518 ECSLayer {
519 writer: Mutex::new(writer),
520 attribute_mapper: self.attribute_mapper,
521 extra_fields: self.extra_fields.unwrap_or_default(),
522 normalize_json: self.normalize_json,
523 span_events: self.span_events,
524 }
525 }
526}
527
528#[derive(thiserror::Error, Debug)]
529pub enum Error {
530 #[error("Extra field cannot be serialized as json")]
531 ExtraFieldNotSerializableAsJson,
532 #[error("Extra field must be serializable as a json map")]
533 ExtraFieldNotAMap,
534 #[error("{0}")]
535 SetGlobalError(#[from] SetGlobalDefaultError),
536 #[error("{0}")]
537 SetLoggerError(#[from] SetLoggerError),
538}
539
540#[cfg(test)]
541mod test {
542
543 use std::{
544 io::{self, sink, BufRead, BufReader},
545 sync::{Arc, Mutex, MutexGuard, Once, TryLockError},
546 thread::{self},
547 };
548
549 use maplit::hashmap;
550 use serde_json::{json, Map, Value};
551 use tracing_log::LogTracer;
552 use tracing_subscriber::{
553 fmt::{format::FmtSpan, MakeWriter, SubscriberBuilder},
554 Layer,
555 };
556
557 use crate::ECSLayerBuilder;
558
559 static START: Once = Once::new();
560
561 pub(crate) struct MockWriter {
562 buf: Arc<Mutex<Vec<u8>>>,
563 }
564
565 impl MockWriter {
566 pub(crate) fn new(buf: Arc<Mutex<Vec<u8>>>) -> Self {
567 Self { buf }
568 }
569
570 pub(crate) fn map_error<Guard>(err: TryLockError<Guard>) -> io::Error {
571 match err {
572 TryLockError::WouldBlock => io::Error::from(io::ErrorKind::WouldBlock),
573 TryLockError::Poisoned(_) => io::Error::from(io::ErrorKind::Other),
574 }
575 }
576
577 pub(crate) fn buf(&self) -> io::Result<MutexGuard<'_, Vec<u8>>> {
578 self.buf.try_lock().map_err(Self::map_error)
579 }
580 }
581
582 impl io::Write for MockWriter {
583 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
584 self.buf()?.write(buf)
585 }
586
587 fn flush(&mut self) -> io::Result<()> {
588 self.buf()?.flush()
589 }
590 }
591
592 #[derive(Clone, Default)]
593 pub(crate) struct MockMakeWriter {
594 buf: Arc<Mutex<Vec<u8>>>,
595 }
596
597 impl MockMakeWriter {
598 pub(crate) fn buf(&self) -> MutexGuard<'_, Vec<u8>> {
599 self.buf.lock().unwrap()
600 }
601 }
602
603 impl<'a> MakeWriter<'a> for MockMakeWriter {
604 type Writer = MockWriter;
605
606 fn make_writer(&'a self) -> Self::Writer {
607 MockWriter::new(self.buf.clone())
608 }
609 }
610
611 fn run_test<T>(builder: ECSLayerBuilder, test: T) -> Vec<Map<String, Value>>
612 where
613 T: FnOnce() -> (),
614 {
615 START.call_once(|| LogTracer::init().unwrap());
616
617 let writer = MockMakeWriter::default();
618
619 let noout = SubscriberBuilder::default().with_writer(|| sink()).finish();
620 let subscriber = builder
621 .build_with_writer(writer.clone())
622 .with_subscriber(noout);
623 tracing_core::dispatcher::with_default(
624 &tracing_core::dispatcher::Dispatch::new(subscriber),
625 test,
626 );
627 let bytes: Vec<u8> = writer.buf().iter().copied().collect();
628 let mut ret = Vec::new();
629 for line in BufReader::new(bytes.as_slice()).lines() {
630 let line = line.expect("Unable to read line");
631 println!("{line}");
632 ret.push(serde_json::from_str(&line).expect("Invalid json line"));
633 }
634 ret
635 }
636
637 #[test]
639 fn test() {
640 let result = run_test(ECSLayerBuilder::default(), || {
641 log::info!("A classic log message outside spans");
642 tracing::info!("A classic tracing event outside spans");
643 let span = tracing::info_span!("span1", foo = "bar", transaction.id = "abcdef");
644 let enter = span.enter();
645 log::info!("A classic log inside a span");
646 tracing::info!(target: "foo_event_target", "A classic tracing event inside a span");
647 drop(enter);
648 log::info!(target: "foo_bar_target", "outside a span");
649 });
650 assert_eq!(result.len(), 5);
651 assert_string(
652 result[0].get("message"),
653 Some("A classic log message outside spans"),
654 );
655 assert_string(
656 result[1].get("message"),
657 Some("A classic tracing event outside spans"),
658 );
659 assert_string(
660 result[2].get("message"),
661 Some("A classic log inside a span"),
662 );
663 assert_string(
664 result[3].get("message"),
665 Some("A classic tracing event inside a span"),
666 );
667 assert_string(result[0].get("span"), None);
668 assert_string(result[1].get("span"), None);
669 assert_string(result[2].get("span").unwrap().get("name"), Some("span1"));
670 assert_string(result[4].get("span.name"), None);
671 assert_string(result[3].get("span").unwrap().get("name"), Some("span1"));
672 assert_string(result[0].get("transaction"), None);
673 assert_string(result[1].get("transaction"), None);
674 assert_string(
675 result[2].get("transaction").unwrap().get("id"),
676 Some("abcdef"),
677 );
678 assert_string(
679 result[3].get("transaction").unwrap().get("id"),
680 Some("abcdef"),
681 );
682 assert_string(result[4].get("transaction"), None);
683
684 assert_string(
686 result[0].get("log").unwrap().get("logger"),
687 Some("tracing_ecs::test"),
688 );
689 assert_string(
690 result[1].get("log").unwrap().get("logger"),
691 Some("tracing_ecs::test"),
692 );
693 assert_string(
694 result[2].get("log").unwrap().get("logger"),
695 Some("tracing_ecs::test"),
696 );
697 assert_string(
698 result[3].get("log").unwrap().get("logger"),
699 Some("foo_event_target"),
700 );
701 assert_string(
702 result[4].get("log").unwrap().get("logger"),
703 Some("foo_bar_target"),
704 );
705
706 assert!(result[0]
708 .get("@timestamp")
709 .cloned()
710 .filter(Value::is_string)
711 .is_some());
712 assert!(result[1]
713 .get("@timestamp")
714 .cloned()
715 .filter(Value::is_string)
716 .is_some());
717 }
718
719 fn assert_string(value: Option<&Value>, expected: Option<&str>) {
720 assert_eq!(
721 value,
722 expected.map(|s| Value::String(s.to_string())).as_ref()
723 );
724 }
725
726 #[test]
728 fn test_extra_fields() {
729 let value = json!({
730 "tags": ["t1", "t2"],
731 "labels": {
732 "env": "prod",
733 "service": "foobar",
734 }
735 });
736 let result = run_test(
737 ECSLayerBuilder::default()
738 .with_extra_fields(&value)
739 .unwrap(),
740 || {
741 log::info!("A classic log message outside spans");
742 tracing::info!("A classic tracing event outside spans");
743 tracing::info!(tags = 123, "A classic tracing event outside spans");
744 },
745 );
746 assert_eq!(result.len(), 3);
747 assert_string(
748 result[0].get("message"),
749 Some("A classic log message outside spans"),
750 );
751 assert_string(
752 result[1].get("message"),
753 Some("A classic tracing event outside spans"),
754 );
755 assert_eq!(result[0].get("tags"), value.get("tags"));
756 assert_eq!(result[1].get("tags"), value.get("tags"));
757 assert_eq!(result[1].get("labels"), value.get("labels"));
758 assert_eq!(result[1].get("labels"), value.get("labels"));
759
760 assert_eq!(result[2].get("tags"), Some(&json!(123)));
762 }
763
764 #[test]
765 fn test_spans() {
766 let result = run_test(ECSLayerBuilder::default(), || {
767 tracing::info!("outside");
768 let sp1 = tracing::info_span!("span1", sp1 = "val1", same = "same1");
769 let _enter1 = sp1.enter();
770 tracing::info!("inside 1");
771 let sp2 = tracing::info_span!("span2", sp2 = "val2", same = "same2");
772 let _enter2 = sp2.enter();
773 tracing::info!("inside 2");
774 tracing::info!(same = "last prevails", "inside 2");
775 });
776 assert_string(result[0].get("span"), None);
778 assert_string(result[1].get("span").unwrap().get("name"), Some("span1"));
779 assert_string(
780 result[2].get("span").unwrap().get("name"),
781 Some("span1:span2"),
782 );
783 assert_string(
784 result[3].get("span").unwrap().get("name"),
785 Some("span1:span2"),
786 );
787
788 assert_string(result[0].get("sp1"), None);
790 assert_string(result[1].get("sp1"), Some("val1"));
791 assert_string(result[2].get("sp1"), Some("val1"));
792 assert_string(result[3].get("sp1"), Some("val1"));
793
794 assert_string(result[0].get("sp2"), None);
795 assert_string(result[1].get("sp2"), None);
796 assert_string(result[2].get("sp2"), Some("val2"));
797 assert_string(result[3].get("sp2"), Some("val2"));
798
799 assert_string(result[0].get("same"), None);
800 assert_string(result[1].get("same"), Some("same1"));
801 assert_string(result[2].get("same"), Some("same2"));
802 assert_string(result[3].get("same"), Some("last prevails"));
803 }
804
805 #[test]
806 fn test_attribute_mapping() {
807 let result = run_test(
808 ECSLayerBuilder::default().with_attribute_mapper(
809 hashmap! {
811 "span1" => hashmap! {
812 "key1" => "foobar"
813 }
814 },
815 ),
816 || {
817 let sp1 = tracing::info_span!("span1", key1 = "val1", other1 = "o1");
818 let _enter1 = sp1.enter();
819 tracing::info!("inside 1");
820 let sp2 = tracing::info_span!("span2", key1 = "val2", other2 = "o2");
821 let _enter2 = sp2.enter();
822 tracing::info!("inside 2");
823 },
824 );
825
826 assert_string(result[0].get("key1"), None);
828 assert_string(result[0].get("foobar"), Some("val1"));
829 assert_string(result[0].get("other1"), Some("o1"));
830 assert_string(result[0].get("other2"), None);
831 assert_string(result[1].get("key1"), Some("val2"));
833 assert_string(result[1].get("foobar"), Some("val1"));
834 assert_string(result[1].get("other1"), Some("o1"));
835 assert_string(result[1].get("other2"), Some("o2"));
836 }
837
838 #[test]
839 fn test_normalization() {
840 let value = json!({
841 "host.hostname": "localhost"
842 });
843 let result = run_test(
844 ECSLayerBuilder::default()
845 .with_extra_fields(&value)
846 .unwrap(),
847 || {
848 tracing::info!(source.ip = "1.2.3.4", "hello world");
849 },
850 );
851 assert_eq!(
852 result[0].get("host").unwrap(),
853 &json!({
854 "hostname": "localhost",
855 })
856 );
857 assert_eq!(
858 result[0].get("source").unwrap(),
859 &json!({
860 "ip": "1.2.3.4",
861 })
862 );
863 }
864 #[test]
865
866 fn test_normalization_disabled() {
867 let value = json!({
868 "host.hostname": "localhost"
869 });
870 let result = run_test(
871 ECSLayerBuilder::default()
872 .normalize_json(false)
873 .with_extra_fields(&value)
874 .unwrap(),
875 || {
876 tracing::info!(source.ip = "1.2.3.4", "hello world");
877 },
878 );
879 assert_eq!(result[0].get("host.hostname").unwrap(), &json!("localhost"));
880 assert_eq!(result[0].get("source.ip").unwrap(), &json!("1.2.3.4"));
881 }
882
883 #[test]
884 fn test_interleaving_logs() {
885 let result = run_test(ECSLayerBuilder::default(), || {
886 let mut join_handles = Vec::new();
887 for i in 0..5 {
888 tracing_core::dispatcher::get_default(|dispatch| {
891 let dispatch = dispatch.clone();
892 join_handles.push(thread::spawn(move || {
893 tracing_core::dispatcher::with_default(&dispatch, move || {
894 for j in 0..1000 {
895 tracing::info!(thread = i, iteration = j, "hello world");
896 }
897 });
898 }));
899 });
900 }
901 for join_handle in join_handles {
902 join_handle.join().unwrap();
903 }
904 });
905 assert_eq!(result.len(), 5000);
906 }
907
908 #[test]
909 fn test_span_instrumentation() {
910 let result = run_test(
911 ECSLayerBuilder::default()
912 .normalize_json(false)
913 .with_span_events(FmtSpan::ENTER | FmtSpan::EXIT),
914 || {
915 let span = tracing::info_span!("test_span", foo = "bar");
916 let _enter = span.enter();
917 tracing::info!("inside span");
918 },
919 );
920
921 assert_eq!(result.len(), 3); assert_eq!(result[0].get("event.kind"), Some(&json!("span")));
924 assert_eq!(result[0].get("event.action"), Some(&json!("enter")));
925 assert_eq!(result[0].get("span.name"), Some(&json!("test_span")));
926 assert_eq!(result[0].get("foo"), Some(&json!("bar")));
927 assert!(result[0].get("span.id").unwrap().is_string());
928
929 assert_eq!(result[1].get("message"), Some(&json!("inside span")));
930 assert_eq!(result[1].get("span.name"), Some(&json!("test_span")));
931 assert_eq!(result[1].get("foo"), Some(&json!("bar")));
932
933 assert_eq!(result[2].get("event.kind"), Some(&json!("span")));
934 assert_eq!(result[2].get("event.action"), Some(&json!("exit")));
935 assert_eq!(result[2].get("span.name"), Some(&json!("test_span")));
936 assert_eq!(result[2].get("foo"), Some(&json!("bar")));
937 assert_eq!(result[2].get("span.id"), result[0].get("span.id"));
938 }
939}