コード例 #1
0
        public void SubscriptionsAreDisposedUponInputDisposal()
        {
            var healthReporter = new Mock <IHealthReporter>();
            var observer       = new TestObserver();
            var ActivityName   = GetRandomName();

            var sources = new List <ActivitySourceConfiguration>(new[]
            {
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    CapturedEvents     = CapturedActivityEvents.Stop
                }
            });

            using (var input = new ActivitySourceInput(new ActivitySourceInputConfiguration {
                Sources = sources
            }, healthReporter.Object))
            {
                input.Subscribe(observer);
            }

            // The input is configured to listen for all activities, but it has been disposed, so no one should be listening anymore
            // and no activity should be created.
            Assert.Null(SourceOne.StartActivity(ActivityName));

            Assert.True(observer.Completed);
            Assert.Null(observer.Error);
            Assert.Empty(observer.Data);
        }
コード例 #2
0
        public void CaptureEverythingConfigurationWorks()
        {
            var healthReporter = new Mock <IHealthReporter>();
            var observer       = new TestObserver();
            var ActivityNameA  = GetRandomName();
            var ActivityNameB  = GetRandomName();

            var sources = new List <ActivitySourceConfiguration>(new[]
            {
                // Empty configuration means capture all activities from all sources,
                // with all data, and both start and stop events
                new ActivitySourceConfiguration {
                }
            });

            using (var input = new ActivitySourceInput(new ActivitySourceInputConfiguration {
                Sources = sources
            }, healthReporter.Object))
            {
                input.Subscribe(observer);

                var activity = SourceOne.StartActivity(ActivityNameA);
                activity.Stop();

                activity = SourceTwo.StartActivity(ActivityNameB);
                activity.AddEvent(new ActivityEvent("ActivityB.Event1"));
                activity.Stop();
            }

            healthReporter.VerifyNoOtherCalls();

            Assert.True(observer.Completed);
            Assert.Null(observer.Error);

            var observed = observer.Data.ToArray();

            // We expect one stop evente for both activities, total 2.
            // There might be more because we are capturing, well, everyting in the system %-)
            Assert.Equal(2, observed.Count(e =>
                                           (OrdinalEquals(e.Payload["Name"], ActivityNameA) && OrdinalEquals(e.Payload["ActivitySourceName"], SourceOneName)) ||
                                           (OrdinalEquals(e.Payload["Name"], ActivityNameB) && OrdinalEquals(e.Payload["ActivitySourceName"], SourceTwoName))
                                           ));

            // Verify that all data was captured by checking for activity event associated with activity B
            var activityB_Stop = observed.First(e =>
                                                OrdinalEquals(e.Payload["Name"], ActivityNameB) &&
                                                OrdinalEquals(e.Payload["ActivitySourceName"], SourceTwoName) &&
                                                e.Payload.ContainsKey("EndTime")
                                                );

            Assert.Equal(1, ((IEnumerable <ActivityEvent>)activityB_Stop.Payload["Events"]).Count(ae => OrdinalEquals(ae.Name, "ActivityB.Event1")));
        }
コード例 #3
0
        public void WarningIssuedIfNoSources()
        {
            var healthReporter = new Mock <IHealthReporter>();
            var input          = new ActivitySourceInput(
                new ActivitySourceInputConfiguration {
                Sources = new List <ActivitySourceConfiguration>()
            },
                healthReporter.Object);

            input.Dispose();

            healthReporter.Verify(hr => hr.ReportWarning(
                                      It.Is <string>(s => s.Contains("no data sources", StringComparison.OrdinalIgnoreCase)),
                                      It.Is <string>(ctx => OrdinalEquals(ctx, EventFlowContextIdentifiers.Configuration))
                                      ), Times.Exactly(1));
            healthReporter.VerifyNoOtherCalls();
        }
コード例 #4
0
        public void AllSourcesCapturedWhenActivitySourceNameIsOmitted()
        {
            var healthReporter = new Mock <IHealthReporter>();
            var observer       = new TestObserver();
            var ActivityNameA  = GetRandomName();
            var ActivityNameB  = GetRandomName();
            var ActivityNameC  = GetRandomName();

            var sources = new List <ActivitySourceConfiguration>(new[]
            {
                new ActivitySourceConfiguration
                {
                    ActivityName   = ActivityNameA,
                    CapturedEvents = CapturedActivityEvents.Stop
                }
            });

            using (var input = new ActivitySourceInput(new ActivitySourceInputConfiguration {
                Sources = sources
            }, healthReporter.Object))
            {
                input.Subscribe(observer);

                var activity = SourceOne.StartActivity(ActivityNameA);
                activity.Stop();

                activity = SourceOne.StartActivity(ActivityNameB);
                Assert.Null(activity); // Nobody is listening

                activity = SourceTwo.StartActivity(ActivityNameA);
                activity.Stop();

                activity = SourceTwo.StartActivity(ActivityNameC);
                Assert.Null(activity);
            }

            healthReporter.VerifyNoOtherCalls();

            Assert.True(observer.Completed);
            Assert.Null(observer.Error);

            var observed = observer.Data.ToArray();

            Assert.Equal(2, observed.Count());
            Assert.All(observed, e => Assert.True(OrdinalEquals(e.Payload["Name"], ActivityNameA)));
        }
コード例 #5
0
        public void BaggageTakesPrecedenceOverTags()
        {
            var healthReporter = new Mock <IHealthReporter>();
            var observer       = new TestObserver();
            var ActivityName   = GetRandomName();

            var sources = new List <ActivitySourceConfiguration>(new[]
            {
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    CapturedEvents     = CapturedActivityEvents.Stop
                }
            });

            using (var input = new ActivitySourceInput(new ActivitySourceInputConfiguration {
                Sources = sources
            }, healthReporter.Object))
            {
                input.Subscribe(observer);

                var activity = SourceOne.StartActivity(ActivityName);
                activity.AddTag("Alpha", "AlphaTag");
                activity.AddBaggage("Alpha", "AlphaBaggage");
                activity.Stop();
            }

            // Complaining about Alpha property name conflict
            healthReporter.Verify(hr => hr.ReportWarning(
                                      It.Is <string>(s => s.Contains("Alpha", StringComparison.OrdinalIgnoreCase)),
                                      It.Is <string>(ctx => OrdinalEquals(ctx, nameof(ActivitySourceInput)))
                                      ), Times.Exactly(1));
            healthReporter.VerifyNoOtherCalls();

            Assert.True(observer.Completed);
            Assert.Null(observer.Error);

            var observed = observer.Data.ToArray();
            var e        = Assert.Single(observed);

            Assert.True(OrdinalEquals(e.Payload["Alpha"], "AlphaBaggage") && OrdinalEquals(e.Payload["Alpha_1"], "AlphaTag"));
        }
コード例 #6
0
        public void BasicActivityTracking()
        {
            var healthReporter = new Mock <IHealthReporter>();
            var observer       = new TestObserver();
            var ActivityName   = GetRandomName();

            var sources = new List <ActivitySourceConfiguration>(new[]
            {
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    ActivityName       = ActivityName,
                    CapturedData       = ActivitySamplingResult.AllData,
                    CapturedEvents     = CapturedActivityEvents.Both
                }
            });

            using (var input = new ActivitySourceInput(new ActivitySourceInputConfiguration {
                Sources = sources
            }, healthReporter.Object))
            {
                input.Subscribe(observer);

                var ctx = new ActivityContext(
                    ActivityTraceId.CreateFromString(WellKnownTraceId),
                    ActivitySpanId.CreateFromString(SpanIdOne),
                    ActivityTraceFlags.None);
                var activity = SourceOne.StartActivity(ActivityName, ActivityKind.Internal, ctx);
                activity.Stop();
            }

            healthReporter.VerifyNoOtherCalls();

            Assert.Equal(2, observer.Data.Count);
            Assert.True(observer.Completed);
            Assert.Null(observer.Error);

            Assert.True(observer.Data.TryDequeue(out EventData e));
            VerifyActivityEvent(e, ActivityName, SourceOneName, CapturedActivityEvents.Start, WellKnownTraceId, SpanIdOne);
            Assert.True(observer.Data.TryDequeue(out e));
            VerifyActivityEvent(e, ActivityName, SourceOneName, CapturedActivityEvents.Stop, WellKnownTraceId, SpanIdOne);
        }
コード例 #7
0
        public void SourceCanBeCreatedAfterInput()
        {
            var healthReporter = new Mock <IHealthReporter>();
            var observer       = new TestObserver();
            var ActivityName   = GetRandomName();

            const string TestSourceName = "EventFlowTestActivitySource";
            var          sources        = new List <ActivitySourceConfiguration>(new[]
            {
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = TestSourceName,
                    CapturedEvents     = CapturedActivityEvents.Stop
                }
            });

            using (var input = new ActivitySourceInput(new ActivitySourceInputConfiguration {
                Sources = sources
            }, healthReporter.Object))
            {
                input.Subscribe(observer);

                using (var activitySouce = new System.Diagnostics.ActivitySource("EventFlowTestActivitySource"))
                {
                    var activity = activitySouce.StartActivity(ActivityName);
                    activity.Stop();
                }
            }

            healthReporter.VerifyNoOtherCalls();

            Assert.True(observer.Completed);
            Assert.Null(observer.Error);

            var observed = observer.Data.ToArray();
            var e        = Assert.Single(observed);

            Assert.Equal(ActivityName, e.Payload["Name"]);
        }
コード例 #8
0
        public void WarnsAboutInnefectiveConfigurationEntries()
        {
            var healthReporter     = new Mock <IHealthReporter>();
            var ActivityNameSuffix = GetRandomName();

            var sources = new List <ActivitySourceConfiguration>(new[]
            {
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    ActivityName       = "ActivityA" + ActivityNameSuffix
                },
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    CapturedData       = ActivitySamplingResult.PropagationData,
                    CapturedEvents     = CapturedActivityEvents.Stop
                },
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    ActivityName       = "ActivityB" + ActivityNameSuffix,
                    CapturedData       = ActivitySamplingResult.None
                }
            });

            using (var input = new ActivitySourceInput(new ActivitySourceInputConfiguration {
                Sources = sources
            }, healthReporter.Object))
            {
            }

            // We expect a warning about an ActivitySource configured with CapturedData = None, since that has no effect
            healthReporter.Verify(hr => hr.ReportWarning(
                                      It.Is <string>(s => s.Contains($"source '{SourceOneName}' has an entry that applies to all activities", StringComparison.OrdinalIgnoreCase)),
                                      It.Is <string>(ctx => OrdinalEquals(ctx, EventFlowContextIdentifiers.Configuration))
                                      ), Times.Exactly(1));
            healthReporter.VerifyNoOtherCalls();
            healthReporter.ResetCalls();

            sources = new List <ActivitySourceConfiguration>(new[]
            {
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    ActivityName       = "ActivityA" + ActivityNameSuffix
                },
                new ActivitySourceConfiguration
                {
                    ActivityName   = "ActivityA" + ActivityNameSuffix,
                    CapturedData   = ActivitySamplingResult.PropagationData,
                    CapturedEvents = CapturedActivityEvents.Stop
                },
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceTwoName,
                    ActivityName       = "ActivityA" + ActivityNameSuffix,
                    CapturedData       = ActivitySamplingResult.None
                }
            });

            using (var input = new ActivitySourceInput(new ActivitySourceInputConfiguration {
                Sources = sources
            }, healthReporter.Object))
            {
            }

            healthReporter.Verify(hr => hr.ReportWarning(
                                      It.Is <string>(s => s.Contains($"an entry that captures activity '{"ActivityA" + ActivityNameSuffix}' for all sources", StringComparison.OrdinalIgnoreCase)),
                                      It.Is <string>(ctx => OrdinalEquals(ctx, EventFlowContextIdentifiers.Configuration))
                                      ), Times.Exactly(1));
            healthReporter.VerifyNoOtherCalls();
            healthReporter.ResetCalls();

            sources = new List <ActivitySourceConfiguration>(new[]
            {
                new ActivitySourceConfiguration
                {
                    CapturedData   = ActivitySamplingResult.PropagationData,
                    CapturedEvents = CapturedActivityEvents.Stop
                },
                new ActivitySourceConfiguration
                {
                    ActivityName = "ActivityA" + ActivityNameSuffix,
                    CapturedData = ActivitySamplingResult.AllData
                }
            });

            using (var input = new ActivitySourceInput(new ActivitySourceInputConfiguration {
                Sources = sources
            }, healthReporter.Object))
            {
            }

            healthReporter.Verify(hr => hr.ReportWarning(
                                      It.Is <string>(s => s.Contains($"entry that applies to all activities from all sources", StringComparison.OrdinalIgnoreCase)),
                                      It.Is <string>(ctx => OrdinalEquals(ctx, EventFlowContextIdentifiers.Configuration))
                                      ), Times.Exactly(1));
            healthReporter.VerifyNoOtherCalls();
            healthReporter.ResetCalls();
        }
コード例 #9
0
        public void CapturedEventsSettingIsEffective()
        {
            var healthReporter     = new Mock <IHealthReporter>();
            var observer           = new TestObserver();
            var ActivityNameSuffix = GetRandomName();

            var sources = new List <ActivitySourceConfiguration>(new[]
            {
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    ActivityName       = "CapturedEventsNone" + ActivityNameSuffix,
                    CapturedData       = ActivitySamplingResult.AllData,
                    CapturedEvents     = CapturedActivityEvents.None
                },
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    ActivityName       = "CapturedDataNone" + ActivityNameSuffix,
                    CapturedData       = ActivitySamplingResult.None,
                    CapturedEvents     = CapturedActivityEvents.Both
                },
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    ActivityName       = "CapturedEventsStart" + ActivityNameSuffix,
                    CapturedData       = ActivitySamplingResult.AllData,
                    CapturedEvents     = CapturedActivityEvents.Start
                },
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    ActivityName       = "CapturedEventsStop" + ActivityNameSuffix,
                    CapturedData       = ActivitySamplingResult.AllData,
                    CapturedEvents     = CapturedActivityEvents.Stop
                },
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    ActivityName       = "CapturedEventsBoth" + ActivityNameSuffix,
                    CapturedData       = ActivitySamplingResult.AllData,
                    CapturedEvents     = CapturedActivityEvents.Both
                },
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    ActivityName       = "CapturedEventsDefault" + ActivityNameSuffix,
                    CapturedData       = ActivitySamplingResult.AllData
                }
            });

            using (var input = new ActivitySourceInput(new ActivitySourceInputConfiguration {
                Sources = sources
            }, healthReporter.Object))
            {
                input.Subscribe(observer);
                var ctx = new ActivityContext(
                    ActivityTraceId.CreateFromString(WellKnownTraceId),
                    ActivitySpanId.CreateFromString(SpanIdOne),
                    ActivityTraceFlags.None);

                foreach (var s in sources)
                {
                    var activity = SourceOne.StartActivity(s.ActivityName, ActivityKind.Internal, ctx);
                    activity?.Stop();
                }
            }

            healthReporter.VerifyNoOtherCalls();

            Assert.True(observer.Completed);
            Assert.Null(observer.Error);

            var observed = observer.Data.ToArray();

            // No events from CapturedEventsNone and CapturedDataNone activities
            Assert.DoesNotContain(observed, o => OrdinalEquals(o.Payload["Name"], "CapturedEventsNone" + ActivityNameSuffix));
            Assert.DoesNotContain(observed, o => OrdinalEquals(o.Payload["Name"], "CapturedDataNone" + ActivityNameSuffix));

            // Only activity start event captured for CapturedEventsStart activity
            Assert.Equal(1, observed.Count(o =>
                                           OrdinalEquals(o.Payload["Name"], "CapturedEventsStart" + ActivityNameSuffix) &&
                                           !o.Payload.ContainsKey("EndTime")
                                           ));
            Assert.DoesNotContain(observed, o =>
                                  OrdinalEquals(o.Payload["Name"], "CapturedEventsStart" + ActivityNameSuffix) &&
                                  o.Payload.ContainsKey("EndTime")
                                  );

            // Only activity stop event captured for CapturedEventStop activity
            Assert.Equal(1, observed.Count(o =>
                                           OrdinalEquals((string)o.Payload["Name"], "CapturedEventsStop" + ActivityNameSuffix) &&
                                           o.Payload.ContainsKey("EndTime")
                                           ));
            Assert.DoesNotContain(observed, o =>
                                  OrdinalEquals((string)o.Payload["Name"], "CapturedEventsStop" + ActivityNameSuffix) &&
                                  !o.Payload.ContainsKey("EndTime")
                                  );

            // Two events (start and stop) are captured for both CapturedEventsBoth and one for CapturedEventDefault activity
            Assert.Equal(2, observed.Count(o => OrdinalEquals((string)o.Payload["Name"], "CapturedEventsBoth" + ActivityNameSuffix)));
            Assert.Equal(1, observed.Count(o => OrdinalEquals((string)o.Payload["Name"], "CapturedEventsDefault" + ActivityNameSuffix)));
        }
コード例 #10
0
        public void CanReadJsonConfiguration()
        {
            string inputConfiguration = @"
                {
                    ""type"": ""ActivitySource"",
                    ""sources"": [
                        {
                            ""ActivitySourceName"": ""Alpha""
                        },
                        {
                            ""ActivityName"": ""SuperImportant""
                        },
                        {
                            ""ActivitySourceName"": ""Bravo"",
                            ""ActivityName"": ""BravoOne""
                        },
                        {
                            ""ActivitySourceName"": ""Bravo"",
                            ""ActivityName"": ""BravoTwo"",
                            ""CapturedData"": ""PropagationData"",
                            ""CapturedEvents"": ""Start""
                        },
                    ]
                }
            ";

            using (var configFile = new TemporaryFile())
            {
                configFile.Write(inputConfiguration);
                var cb = new ConfigurationBuilder();
                cb.AddJsonFile(configFile.FilePath);
                var configuration = cb.Build();

                var healthReporter = new Mock <IHealthReporter>();
                var input          = new ActivitySourceInput(configuration, healthReporter.Object);

                healthReporter.VerifyNoOtherCalls();
                Assert.Collection(input.Configuration.Sources,
                                  sc => {
                    Assert.Equal("Alpha", sc.ActivitySourceName, StringComparer.Ordinal);
                    Assert.True(string.IsNullOrEmpty(sc.ActivityName));
                    Assert.Equal(ActivitySamplingResult.AllData, sc.CapturedData);
                    Assert.Equal(CapturedActivityEvents.Stop, sc.CapturedEvents);
                },
                                  sc => {
                    Assert.True(string.IsNullOrEmpty(sc.ActivitySourceName));
                    Assert.Equal("SuperImportant", sc.ActivityName, StringComparer.Ordinal);
                    Assert.Equal(ActivitySamplingResult.AllData, sc.CapturedData);
                    Assert.Equal(CapturedActivityEvents.Stop, sc.CapturedEvents);
                },
                                  sc => {
                    Assert.Equal("Bravo", sc.ActivitySourceName, StringComparer.Ordinal);
                    Assert.Equal("BravoOne", sc.ActivityName, StringComparer.Ordinal);
                    Assert.Equal(ActivitySamplingResult.AllData, sc.CapturedData);
                    Assert.Equal(CapturedActivityEvents.Stop, sc.CapturedEvents);
                },
                                  sc => {
                    Assert.Equal("Bravo", sc.ActivitySourceName, StringComparer.OrdinalIgnoreCase);
                    Assert.Equal("BravoTwo", sc.ActivityName, StringComparer.Ordinal);
                    Assert.Equal(ActivitySamplingResult.PropagationData, sc.CapturedData);
                    Assert.Equal(CapturedActivityEvents.Start, sc.CapturedEvents);
                }
                                  );
            }
        }
コード例 #11
0
        public void CapturedDataSettingIsEffective()
        {
            var healthReporter     = new Mock <IHealthReporter>();
            var observer           = new TestObserver();
            var ActivityNameSuffix = GetRandomName();

            var sources = new List <ActivitySourceConfiguration>(new[]
            {
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    ActivityName       = "CapturedDataPropagation" + ActivityNameSuffix,
                    CapturedData       = ActivitySamplingResult.PropagationData
                },
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    ActivityName       = "CapturedDataAll" + ActivityNameSuffix,
                    CapturedData       = ActivitySamplingResult.AllData
                },
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    ActivityName       = "CapturedDataAllRecorded" + ActivityNameSuffix,
                    CapturedData       = ActivitySamplingResult.AllDataAndRecorded
                },
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    ActivityName       = "CapturedDataDefault" + ActivityNameSuffix
                },
                new ActivitySourceConfiguration
                {
                    ActivitySourceName = SourceOneName,
                    ActivityName       = "Outer" + ActivityNameSuffix
                },
            });

            string outerActivityId = null;

            using (var input = new ActivitySourceInput(new ActivitySourceInputConfiguration {
                Sources = sources
            }, healthReporter.Object))
            {
                input.Subscribe(observer);

                var ctx = new ActivityContext(
                    ActivityTraceId.CreateFromString(WellKnownTraceId),
                    ActivitySpanId.CreateFromString(SpanIdOne),
                    ActivityTraceFlags.None);
                var outer = SourceOne.StartActivity("Outer" + ActivityNameSuffix, ActivityKind.Server, ctx);
                outer.AddBaggage("Outer.Baggage1", "Outer.Baggage1.Value");
                outerActivityId = outer.SpanId.ToHexString();

                var activity = SourceOne.StartActivity("CapturedDataPropagation" + ActivityNameSuffix, ActivityKind.Internal, null,
                                                       new Dictionary <string, object>
                {
                    ["CapturedDataPropagation.Tag1"] = "CapturedDataPropagation.Tag1Value",
                    ["CapturedDataPropagation.Tag2"] = new NamedObject("CapturedDataPropagation.Tag2Value")
                },
                                                       new[] {
                    new ActivityLink(new ActivityContext(
                                         ActivityTraceId.CreateFromString(WellKnownTraceIdTwo),
                                         ActivitySpanId.CreateFromString(SpanIdTwo),
                                         ActivityTraceFlags.None
                                         ))
                });
                Thread.Sleep(30);
                activity.AddEvent(new ActivityEvent("CapturedDataPropagation.Event1"));
                activity.Stop();

                // Add extra baggage to outer activity and make sure it is captured by subsequent activities
                outer.AddBaggage("Outer.Baggage2", "Outer.Baggage2.Value");

                activity = SourceOne.StartActivity("CapturedDataAll" + ActivityNameSuffix, ActivityKind.Internal, null,
                                                   new Dictionary <string, object>
                {
                    ["CapturedDataAll.Tag1"] = "CapturedDataAll.Tag1Value",
                    ["CapturedDataAll.Tag2"] = new NamedObject("CapturedDataAll.Tag2Value")
                },
                                                   new[] {
                    new ActivityLink(new ActivityContext(
                                         ActivityTraceId.CreateFromString(WellKnownTraceIdTwo),
                                         ActivitySpanId.CreateFromString(SpanIdThree),
                                         ActivityTraceFlags.None
                                         ))
                });
                Thread.Sleep(30);
                activity.AddEvent(new ActivityEvent("CapturedDataAll.Event1"));
                activity.Stop();

                activity = SourceOne.StartActivity("CapturedDataAllRecorded" + ActivityNameSuffix, ActivityKind.Internal, null,
                                                   new Dictionary <string, object>
                {
                    ["CapturedDataAllRecorded.Tag1"] = "CapturedDataAllRecorded.Tag1Value",
                    ["CapturedDataAllRecorded.Tag2"] = new NamedObject("CapturedDataAllRecorded.Tag2Value")
                },
                                                   new[] {
                    new ActivityLink(new ActivityContext(
                                         ActivityTraceId.CreateFromString(WellKnownTraceIdTwo),
                                         ActivitySpanId.CreateFromString(SpanIdThree),
                                         ActivityTraceFlags.None
                                         ))
                });
                Thread.Sleep(30);
                activity.AddEvent(new ActivityEvent("CapturedDataAllRecorded.Event1"));
                activity.Stop();

                ctx = new ActivityContext(
                    ActivityTraceId.CreateFromString(WellKnownTraceId),
                    ActivitySpanId.CreateFromString(outerActivityId),
                    ActivityTraceFlags.None);
                activity = SourceOne.StartActivity("CapturedDataDefault" + ActivityNameSuffix, ActivityKind.Internal, null,
                                                   new Dictionary <string, object>
                {
                    ["CapturedDataDefault.Tag1"] = "CapturedDataDefault.Tag1Value",
                    ["CapturedDataDefault.Tag2"] = new NamedObject("CapturedDataDefault.Tag2Value")
                },
                                                   new[] {
                    new ActivityLink(new ActivityContext(
                                         ActivityTraceId.CreateFromString(WellKnownTraceIdTwo),
                                         ActivitySpanId.CreateFromString(SpanIdTwo),
                                         ActivityTraceFlags.None
                                         ))
                });
                Thread.Sleep(30);
                activity.AddEvent(new ActivityEvent("CapturedDataDefault.Event1"));
                activity.Stop();

                outer.Stop();
            }

            healthReporter.VerifyNoOtherCalls();

            // We expect one event the outer activity and 4 (stop) events for the inner ones
            Assert.Equal(5, observer.Data.Count);
            Assert.True(observer.Completed);
            Assert.Null(observer.Error);

            var observed = observer.Data.ToArray();

            // CapturedData == Propaation should capture baggage, but not links, tags, or events
            var ae = observed.First(e =>
                                    OrdinalEquals(e.Payload["Name"], "CapturedDataPropagation" + ActivityNameSuffix) &&
                                    e.Payload.ContainsKey("EndTime"));

            VerifyActivityEvent(ae,
                                "CapturedDataPropagation" + ActivityNameSuffix, SourceOneName, CapturedActivityEvents.Stop,
                                WellKnownTraceId, outerActivityId, false, ActivityKind.Internal,
                                new Dictionary <string, Func <object, bool> > {
                ["Outer.Baggage1"] = (val) => OrdinalEquals(val, "Outer.Baggage1.Value")
            },
                                new[] { "Links", "Tags", "Events" });

            // CaptureDataAll and CaptureDataAllRecorded should have baggage, links, tags, and events
            // They only differ by IsRecorded flag value
            ae = observed.First(e =>
                                OrdinalEquals(e.Payload["Name"], "CapturedDataAll" + ActivityNameSuffix) &&
                                e.Payload.ContainsKey("EndTime"));
            VerifyActivityEvent(ae,
                                "CapturedDataAll" + ActivityNameSuffix, SourceOneName, CapturedActivityEvents.Stop,
                                WellKnownTraceId, outerActivityId, false, ActivityKind.Internal,
                                new Dictionary <string, Func <object, bool> >
            {
                ["Outer.Baggage1"] = (val) => OrdinalEquals(val, "Outer.Baggage1.Value"),
                ["Outer.Baggage2"] = (val) => OrdinalEquals(val, "Outer.Baggage2.Value"),
                ["Events"]         = (evnts) =>
                {
                    Assert.Collection <ActivityEvent>((IEnumerable <ActivityEvent>)evnts, e => Assert.Equal("CapturedDataAll.Event1", e.Name, StringComparer.Ordinal));
                    return(true);
                },
                ["CapturedDataAll.Tag1"] = (val) => OrdinalEquals(val, "CapturedDataAll.Tag1Value"),
                ["CapturedDataAll.Tag2"] = (val) => OrdinalEquals(((NamedObject)val).Name, "CapturedDataAll.Tag2Value"),
                ["Links"] = (links) =>
                {
                    Assert.Collection <ActivityLink>((IEnumerable <ActivityLink>)links,
                                                     l => Assert.True(
                                                         OrdinalEquals(l.Context.TraceId.ToHexString(), WellKnownTraceIdTwo) &&
                                                         OrdinalEquals(l.Context.SpanId.ToHexString(), SpanIdThree))
                                                     );
                    return(true);
                }
            }
                                );

            ae = observed.First(e =>
                                OrdinalEquals(e.Payload["Name"], "CapturedDataAllRecorded" + ActivityNameSuffix) &&
                                e.Payload.ContainsKey("EndTime"));
            VerifyActivityEvent(ae,
                                "CapturedDataAllRecorded" + ActivityNameSuffix, SourceOneName, CapturedActivityEvents.Stop,
                                WellKnownTraceId, outerActivityId, true, ActivityKind.Internal,
                                new Dictionary <string, Func <object, bool> >
            {
                ["Outer.Baggage1"] = (val) => OrdinalEquals(val, "Outer.Baggage1.Value"),
                ["Outer.Baggage2"] = (val) => OrdinalEquals(val, "Outer.Baggage2.Value"),
                ["Events"]         = (evnts) =>
                {
                    Assert.Collection <ActivityEvent>((IEnumerable <ActivityEvent>)evnts, e => Assert.Equal("CapturedDataAllRecorded.Event1", e.Name, StringComparer.Ordinal));
                    return(true);
                },
                ["CapturedDataAllRecorded.Tag1"] = (val) => OrdinalEquals(val, "CapturedDataAllRecorded.Tag1Value"),
                ["CapturedDataAllRecorded.Tag2"] = (val) => OrdinalEquals(((NamedObject)val).Name, "CapturedDataAllRecorded.Tag2Value"),
                ["Links"] = (links) =>
                {
                    Assert.Collection <ActivityLink>((IEnumerable <ActivityLink>)links,
                                                     l => Assert.True(
                                                         OrdinalEquals(l.Context.TraceId.ToHexString(), WellKnownTraceIdTwo) &&
                                                         OrdinalEquals(l.Context.SpanId.ToHexString(), SpanIdThree))
                                                     );
                    return(true);
                }
            }
                                );

            // The default for data capturing is AllData, so all data should be captured, but the recording flag should not be set.
            ae = observed.First(e =>
                                OrdinalEquals(e.Payload["Name"], "CapturedDataDefault" + ActivityNameSuffix) &&
                                e.Payload.ContainsKey("EndTime"));
            VerifyActivityEvent(ae,
                                "CapturedDataDefault" + ActivityNameSuffix, SourceOneName, CapturedActivityEvents.Stop,
                                WellKnownTraceId, outerActivityId, false, ActivityKind.Internal,
                                new Dictionary <string, Func <object, bool> >
            {
                ["Outer.Baggage1"] = (val) => OrdinalEquals(val, "Outer.Baggage1.Value"),
                ["Outer.Baggage2"] = (val) => OrdinalEquals(val, "Outer.Baggage2.Value"),
                ["Events"]         = (evnts) =>
                {
                    Assert.Collection <ActivityEvent>((IEnumerable <ActivityEvent>)evnts, e => Assert.Equal("CapturedDataDefault.Event1", e.Name, StringComparer.Ordinal));
                    return(true);
                },
                ["CapturedDataDefault.Tag1"] = (val) => OrdinalEquals(val, "CapturedDataDefault.Tag1Value"),
                ["CapturedDataDefault.Tag2"] = (val) => OrdinalEquals(((NamedObject)val).Name, "CapturedDataDefault.Tag2Value"),
                ["Links"] = (links) =>
                {
                    Assert.Collection <ActivityLink>((IEnumerable <ActivityLink>)links,
                                                     l => Assert.True(
                                                         OrdinalEquals(l.Context.TraceId.ToHexString(), WellKnownTraceIdTwo) &&
                                                         OrdinalEquals(l.Context.SpanId.ToHexString(), SpanIdTwo))
                                                     );
                    return(true);
                }
            }
                                );

            // Phew! That was a lot of checking :-)
        }