public IntakeV2EventsController(MockApmServer mockApmServer)
        {
            _mockApmServer = mockApmServer;
            _logger        = mockApmServer.InternalLogger.Scoped(ThisClassName + "#" + RuntimeHelpers.GetHashCode(this).ToString("X"));

            _logger.Debug()?.Log("Constructed with mock APM Server: {MockApmServer}", _mockApmServer);
        }
예제 #2
0
        protected TestsBase(
            ITestOutputHelper xUnitOutputHelper,
            bool startMockApmServer = true,
            IDictionary <string, string> envVarsToSetForSampleAppPool = null,
            bool sampleAppShouldHaveAccessToPerfCounters = false,
            bool sampleAppLogEnabled = true
            ) : base(xUnitOutputHelper)
        {
            _logger = LoggerBase.Scoped(ThisClassName);

            MockApmServer       = new MockApmServer(_logger, TestDisplayName);
            _iisAdministration  = new IisAdministration(_logger);
            _startMockApmServer = startMockApmServer;
            SampleAppShouldHaveAccessToPerfCounters = sampleAppShouldHaveAccessToPerfCounters;

            _mockApmServerPort = _startMockApmServer ? MockApmServer.FindAvailablePortToListen() : ConfigConsts.DefaultValues.ApmServerPort;

            _sampleAppLogEnabled  = sampleAppLogEnabled;
            _sampleAppLogFilePath = GetSampleAppLogFilePath();

            EnvVarsToSetForSampleAppPool = envVarsToSetForSampleAppPool == null
                                ? new Dictionary <string, string>()
                                : new Dictionary <string, string>(envVarsToSetForSampleAppPool);
            EnvVarsToSetForSampleAppPool.TryAdd(ConfigConsts.EnvVarNames.ServerUrls, BuildApmServerUrl(_mockApmServerPort));

            if (_sampleAppLogEnabled)
            {
                EnvVarsToSetForSampleAppPool.TryAdd(LoggingConfig.LogFileEnvVarName, _sampleAppLogFilePath);
            }

            EnvVarsToSetForSampleAppPool.TryAdd(ConfigConsts.EnvVarNames.FlushInterval, "10ms");
        }
        public async Task CaptureAutoInstrumentedSpans(string targetFramework)
        {
            if (!TestEnvironment.IsWindows)
            {
                return;
            }

            var apmLogger = new InMemoryBlockingLogger(Elastic.Apm.Logging.LogLevel.Error);
            var apmServer = new MockApmServer(apmLogger, nameof(CaptureAutoInstrumentedSpans));
            var port      = apmServer.FindAvailablePortToListen();

            apmServer.RunInBackground(port);

            using (var profiledApplication = new ProfiledApplication("OracleManagedDataAccessSample"))
            {
                IDictionary <string, string> environmentVariables = new Dictionary <string, string>
                {
                    ["ELASTIC_APM_SERVER_URL"]      = $"http://localhost:{port}",
                    ["ORACLE_CONNECTION_STRING"]    = _fixture.ConnectionString,
                    ["ELASTIC_APM_DISABLE_METRICS"] = "*",
                    // to fix ORA-01882 Timezone region not found on CI.
                    ["TZ"] = "GMT",
                    ["ELASTIC_APM_EXIT_SPAN_MIN_DURATION"]   = "0",
                    ["ELASTIC_APM_SPAN_COMPRESSION_ENABLED"] = "false"
                };

                profiledApplication.Start(
                    targetFramework,
                    TimeSpan.FromMinutes(2),
                    environmentVariables,
                    null,
                    line => _output.WriteLine(line.Line),
                    exception => _output.WriteLine($"{exception}"));
            }

            apmServer.ReceivedData.Transactions.Should().HaveCount(2);
            apmServer.ReceivedData.Spans.Should().HaveCount(AdoNetTestData.DbRunnerExpectedTotalSpans + AdoNetTestData.OracleProviderExpectedSpans);

            var testSpans = apmServer.ReceivedData.Spans
                            .Where(s => !s.Name.StartsWith(AdoNetTestData.OracleProviderSpanNameStart))
                            .ToList();

            var genericTransaction = apmServer.ReceivedData.Transactions.FirstOrDefault(t => t.Name == "RunAllAsync<TDbCommand>");

            genericTransaction.Should().NotBeNull();

            var genericSpans = testSpans.Where(s => s.TransactionId == genericTransaction.Id).ToList();

            genericSpans.Should().HaveCount(AdoNetTestData.DbRunnerExpectedRunAllAsyncSpans);

            var baseTransaction = apmServer.ReceivedData.Transactions.FirstOrDefault(t => t.Name == "RunBaseTypesAsync");

            baseTransaction.Should().NotBeNull();

            var baseSpans = testSpans.Where(s => s.TransactionId == baseTransaction.Id).ToList();

            baseSpans.Should().HaveCount(AdoNetTestData.DbRunnerExpectedRunBaseTypesAsyncSpans);

            await apmServer.StopAsync();
        }
        public IntakeV2EventsController(MockApmServer mockApmServer)
        {
            _mockApmServer = mockApmServer;
            _logger        = mockApmServer.Logger.Scoped(nameof(IntakeV2EventsController));

            _logger.Debug()?.Log("Constructed with mock APM Server: {MockApmServer}", _mockApmServer);
        }
예제 #5
0
        protected TestsBase(ITestOutputHelper xUnitOutputHelper,
                            bool startMockApmServer = true,
                            IDictionary <string, string> envVarsToSetForSampleAppPool = null,
                            bool sampleAppShouldHaveAccessToPerfCounters = false,
                            bool sampleAppLogEnabled = true
                            )
        {
            _xUnitOutputHelper = xUnitOutputHelper;
            _logger            = new ToAllSinksLogger(_xUnitOutputHelper).Scoped(nameof(TestsBase));

            _logger.Info()?.Log("Starting test: {FullUnitTestName}", GetCurrentTestDisplayName(_xUnitOutputHelper));

            _mockApmServer      = new MockApmServer(_logger, GetCurrentTestDisplayName(_xUnitOutputHelper));
            _iisAdministration  = new IisAdministration(_logger);
            _startMockApmServer = startMockApmServer;
            SampleAppShouldHaveAccessToPerfCounters = sampleAppShouldHaveAccessToPerfCounters;

            _mockApmServerPort = _startMockApmServer ? _mockApmServer.FindAvailablePortToListen() : ConfigConsts.DefaultValues.ApmServerPort;

            _sampleAppLogEnabled  = sampleAppLogEnabled;
            _sampleAppLogFilePath = GetSampleAppLogFilePath();

            _envVarsToSetForSampleAppPool = envVarsToSetForSampleAppPool == null
                                ? new Dictionary <string, string>()
                                : new Dictionary <string, string>(envVarsToSetForSampleAppPool);
            _envVarsToSetForSampleAppPool.TryAdd(ConfigConsts.EnvVarNames.ServerUrls, BuildApmServerUrl(_mockApmServerPort));

            if (_sampleAppLogEnabled)
            {
                _envVarsToSetForSampleAppPool.TryAdd(LoggingConfig.LogFileEnvVarName, _sampleAppLogFilePath);
            }

            _envVarsToSetForSampleAppPool.TryAdd(ConfigConsts.EnvVarNames.FlushInterval, "10ms");
        }
예제 #6
0
        public async Task CaptureAutoInstrumentedSpans(string targetFramework)
        {
            var apmLogger = new InMemoryBlockingLogger(Elastic.Apm.Logging.LogLevel.Error);
            var apmServer = new MockApmServer(apmLogger, nameof(CaptureAutoInstrumentedSpans));
            var port      = apmServer.FindAvailablePortToListen();

            apmServer.RunInBackground(port);

            using (var profiledApplication = new ProfiledApplication("MySqlDataSample"))
            {
                IDictionary <string, string> environmentVariables = new Dictionary <string, string>
                {
                    ["ELASTIC_APM_SERVER_URL"]               = $"http://localhost:{port}",
                    ["MYSQL_CONNECTION_STRING"]              = _fixture.ConnectionString,
                    ["ELASTIC_APM_DISABLE_METRICS"]          = "*",
                    ["ELASTIC_APM_EXIT_SPAN_MIN_DURATION"]   = "0",
                    ["ELASTIC_APM_SPAN_COMPRESSION_ENABLED"] = "false"
                };

                profiledApplication.Start(
                    targetFramework,
                    TimeSpan.FromMinutes(2),
                    environmentVariables,
                    null,
                    line => _output.WriteLine(line.Line),
                    exception => _output.WriteLine($"{exception}"));
            }

            // RunAllAsync<TDbCommand> transaction
            // RunBaseTypesAsync transaction
            apmServer.ReceivedData.Transactions.Should().HaveCount(2);

            // The first MySqlCommand on an opened MySqlConnection executes an additional
            // command that the profiler instrumentation will create a span for. Since there
            // are two connections opened, 1 for RunAllAsync<TDbCommand> and 1 for RunBaseTypesAsync,
            // expect 2 additional spans
            apmServer.ReceivedData.Spans.Should().HaveCount(AdoNetTestData.DbRunnerExpectedTotalSpans + 2);

            var genericTransaction = apmServer.ReceivedData.Transactions.FirstOrDefault(t => t.Name == "RunAllAsync<TDbCommand>");

            genericTransaction.Should().NotBeNull();

            var genericSpans = apmServer.ReceivedData.Spans.Where(s => s.TransactionId == genericTransaction.Id).ToList();

            genericSpans.Should().HaveCount(AdoNetTestData.DbRunnerExpectedRunAllAsyncSpans + 1);

            var baseTransaction = apmServer.ReceivedData.Transactions.FirstOrDefault(t => t.Name == "RunBaseTypesAsync");

            baseTransaction.Should().NotBeNull();

            var baseSpans = apmServer.ReceivedData.Spans.Where(s => s.TransactionId == baseTransaction.Id).ToList();

            baseSpans.Should().HaveCount(AdoNetTestData.DbRunnerExpectedRunBaseTypesAsyncSpans + 1);

            await apmServer.StopAsync();
        }
        public async Task CaptureAutoInstrumentedSpans(string targetFramework, string npgsqlVersion)
        {
            var apmLogger = new InMemoryBlockingLogger(Elastic.Apm.Logging.LogLevel.Error);
            var apmServer = new MockApmServer(apmLogger, nameof(CaptureAutoInstrumentedSpans));
            var port      = apmServer.FindAvailablePortToListen();

            apmServer.RunInBackground(port);

            using (var profiledApplication = new ProfiledApplication("NpgsqlSample"))
            {
                var environmentVariables = new Dictionary <string, string>
                {
                    ["ELASTIC_APM_SERVER_URL"]               = $"http://localhost:{port}",
                    ["POSTGRES_CONNECTION_STRING"]           = _fixture.ConnectionString,
                    ["ELASTIC_APM_DISABLE_METRICS"]          = "*",
                    ["ELASTIC_APM_EXIT_SPAN_MIN_DURATION"]   = "0",
                    ["ELASTIC_APM_SPAN_COMPRESSION_ENABLED"] = "false"
                };

                var msBuildProperties = npgsqlVersion is null
                                        ? null
                                        : new Dictionary <string, string> {
                    ["NpgsqlVersion"] = npgsqlVersion
                };

                profiledApplication.Start(
                    targetFramework,
                    TimeSpan.FromMinutes(2),
                    environmentVariables,
                    msBuildProperties,
                    line => _output.WriteLine(line.Line),
                    exception => _output.WriteLine($"{exception}"));
            }

            apmServer.ReceivedData.Transactions.Should().HaveCount(2);
            apmServer.ReceivedData.Spans.Should().HaveCount(AdoNetTestData.DbRunnerExpectedTotalSpans);

            var genericTransaction = apmServer.ReceivedData.Transactions.FirstOrDefault(t => t.Name == "RunAllAsync<TDbCommand>");

            genericTransaction.Should().NotBeNull();

            var genericSpans = apmServer.ReceivedData.Spans.Where(s => s.TransactionId == genericTransaction.Id).ToList();

            genericSpans.Should().HaveCount(AdoNetTestData.DbRunnerExpectedRunAllAsyncSpans);

            var baseTransaction = apmServer.ReceivedData.Transactions.FirstOrDefault(t => t.Name == "RunBaseTypesAsync");

            baseTransaction.Should().NotBeNull();

            var baseSpans = apmServer.ReceivedData.Spans.Where(s => s.TransactionId == baseTransaction.Id).ToList();

            baseSpans.Should().HaveCount(AdoNetTestData.DbRunnerExpectedRunBaseTypesAsyncSpans);

            await apmServer.StopAsync();
        }
예제 #8
0
        public async Task Auto_Instrument_With_StartupHook_Should_Capture_Error(string targetFramework)
        {
            var apmLogger = new InMemoryBlockingLogger(LogLevel.Error);
            var apmServer = new MockApmServer(apmLogger, nameof(Auto_Instrument_With_StartupHook_Should_Capture_Error));
            var port      = apmServer.FindAvailablePortToListen();

            apmServer.RunInBackground(port);
            var transactionWaitHandle = new ManualResetEvent(false);
            var errorWaitHandle       = new ManualResetEvent(false);

            apmServer.OnReceive += o =>
            {
                if (o is TransactionDto)
                {
                    transactionWaitHandle.Set();
                }
                if (o is ErrorDto)
                {
                    errorWaitHandle.Set();
                }
            };

            using (var sampleApp = new SampleApplication())
            {
                var environmentVariables = new Dictionary <string, string>
                {
                    [EnvVarNames.ServerUrl]     = $"http://localhost:{port}",
                    [EnvVarNames.CloudProvider] = "none"
                };

                var uri     = sampleApp.Start(targetFramework, environmentVariables);
                var builder = new UriBuilder(uri)
                {
                    Path = "Home/Exception"
                };
                var client   = new HttpClient();
                var response = await client.GetAsync(builder.Uri);

                response.IsSuccessStatusCode.Should().BeFalse();

                transactionWaitHandle.WaitOne(TimeSpan.FromMinutes(2));
                apmServer.ReceivedData.Transactions.Should().HaveCount(1);

                var transaction = apmServer.ReceivedData.Transactions.First();
                transaction.Name.Should().Be("GET Home/Exception");

                errorWaitHandle.WaitOne(TimeSpan.FromMinutes(2));
                apmServer.ReceivedData.Errors.Should().HaveCount(1);

                var error = apmServer.ReceivedData.Errors.First();
                error.Culprit.Should().Be("Elastic.Apm.StartupHook.Sample.Controllers.HomeController");
            }

            await apmServer.StopAsync();
        }
        public async Task Auto_Instrument_With_StartupHook_Should_Capture_Metadata(
            string targetFramework,
            string expectedRuntimeName,
            string expectedFrameworkVersion)
        {
            var apmLogger = new InMemoryBlockingLogger(LogLevel.Error);
            var apmServer = new MockApmServer(apmLogger, nameof(Auto_Instrument_With_StartupHook_Should_Capture_Metadata));
            var port      = apmServer.FindAvailablePortToListen();

            apmServer.RunInBackground(port);

            var waitHandle = new ManualResetEvent(false);

            apmServer.OnReceive += o =>
            {
                if (o is MetadataDto)
                {
                    waitHandle.Set();
                }
            };

            using (var sampleApp = new SampleApplication())
            {
                var environmentVariables = new Dictionary <string, string>
                {
                    [EnvVarNames.ServerUrl]     = $"http://localhost:{port}",
                    [EnvVarNames.CloudProvider] = "none"
                };

                var uri      = sampleApp.Start(targetFramework, environmentVariables);
                var client   = new HttpClient();
                var response = await client.GetAsync(uri);

                response.IsSuccessStatusCode.Should().BeTrue();

                waitHandle.WaitOne(TimeSpan.FromMinutes(2));
                apmServer.ReceivedData.Metadata.Should().HaveCountGreaterOrEqualTo(1);

                var metadata = apmServer.ReceivedData.Metadata.First();
                metadata.Service.Runtime.Name.Should().Be(expectedRuntimeName);
                metadata.Service.Framework.Name.Should().Be("ASP.NET Core");
                metadata.Service.Framework.Version.Should().Be(expectedFrameworkVersion);
            }

            await apmServer.StopAsync();
        }
예제 #10
0
        protected TestsBase(ITestOutputHelper xUnitOutputHelper,
                            bool startMockApmServer = true,
                            Dictionary <string, string> envVarsToSetForSampleAppPool = null,
                            bool sampleAppShouldHaveAccessToPerfCounters             = false
                            )
        {
            _logger             = new XunitOutputLogger(xUnitOutputHelper).Scoped(nameof(TestsBase));
            _mockApmServer      = new MockApmServer(_logger, GetCurrentTestName(xUnitOutputHelper));
            _iisAdministration  = new IisAdministration(_logger);
            _startMockApmServer = startMockApmServer;
            SampleAppShouldHaveAccessToPerfCounters = sampleAppShouldHaveAccessToPerfCounters;

            _mockApmServerPort = _startMockApmServer ? _mockApmServer.FindAvailablePortToListen() : ConfigConsts.DefaultValues.ApmServerPort;

            _envVarsToSetForSampleAppPool = envVarsToSetForSampleAppPool == null
                                ? new Dictionary <string, string>()
                                : new Dictionary <string, string>(envVarsToSetForSampleAppPool);
            _envVarsToSetForSampleAppPool.TryAdd(ConfigConsts.EnvVarNames.ServerUrls, $"http://localhost:{_mockApmServerPort}");
        }
예제 #11
0
        public async Task CaptureAutoInstrumentedSpans(string targetFramework)
        {
            var apmLogger = new InMemoryBlockingLogger(Logging.LogLevel.Error);
            var apmServer = new MockApmServer(apmLogger, nameof(CaptureAutoInstrumentedSpans));
            var port      = apmServer.FindAvailablePortToListen();

            apmServer.RunInBackground(port);

            using (var profiledApplication = new ProfiledApplication("RabbitMqSample"))
            {
                IDictionary <string, string> environmentVariables = new Dictionary <string, string>
                {
                    ["RABBITMQ_HOST"]                     = _fixture.ConnectionString,
                    ["ELASTIC_APM_SERVER_URL"]            = $"http://localhost:{port}",
                    ["ELASTIC_APM_DISABLE_METRICS"]       = "*",
                    ["ELASTIC_APM_IGNORE_MESSAGE_QUEUES"] = "test-ignore-exchange-name,test-ignore-queue-name"
                };

                profiledApplication.Start(
                    targetFramework,
                    TimeSpan.FromMinutes(2),
                    environmentVariables,
                    null,
                    line => _output.WriteLine(line.Line),
                    exception => _output.WriteLine($"{exception}"));
            }

            var transactions = apmServer.ReceivedData.Transactions;
            var spans        = apmServer.ReceivedData.Spans;

            transactions.Should().HaveCount(9);

            var ignoreTransaction = transactions.Single(t => t.Name == "PublishAndGetIgnore");

            // don't capture any spans for ignored queues and messages
            spans.Where(s => s.TransactionId == ignoreTransaction.Id).Should().BeEmpty();

            var publishAndGetTransaction = transactions.Single(t => t.Name == "PublishAndGet");

            spans.Where(s => s.TransactionId == publishAndGetTransaction.Id).Should().HaveCount(3, "PublishAndGet");

            var publishAndGetDefaultTransaction = transactions.Single(t => t.Name == "PublishAndGetDefault");

            spans.Where(s => s.TransactionId == publishAndGetDefaultTransaction.Id).Should().HaveCount(3, "PublishAndGetDefault");

            var senderTransactions = transactions.Where(t => t.Name == "PublishToConsumer").ToList();

            senderTransactions.Should().HaveCount(3, "PublishToConsumer");

            var consumeTransactions = transactions.Where(t => t.Name.StartsWith("RabbitMQ RECEIVE from")).ToList();

            consumeTransactions.Should().HaveCount(3, "RabbitMQ RECEIVE from");

            foreach (var senderTransaction in senderTransactions)
            {
                var senderSpan = spans.FirstOrDefault(s => s.TransactionId == senderTransaction.Id);
                senderSpan.Should().NotBeNull();

                var tracingTransaction = consumeTransactions.FirstOrDefault(t => t.TraceId == senderTransaction.TraceId);
                tracingTransaction.Should().NotBeNull();
                tracingTransaction.ParentId.Should().Be(senderSpan.Id);
            }

            foreach (var consumeTransaction in consumeTransactions)
            {
                spans.Where(s => s.TransactionId == consumeTransaction.Id).Should().HaveCount(1);
            }

            await apmServer.StopAsync();
        }
예제 #12
0
        public async Task Auto_Instrument_With_StartupHook(string template, string name, string targetFramework, string path)
        {
            var apmLogger = new InMemoryBlockingLogger(LogLevel.Trace);
            var apmServer = new MockApmServer(apmLogger, nameof(Auto_Instrument_With_StartupHook));
            var port      = apmServer.FindAvailablePortToListen();

            apmServer.RunInBackground(port);

            var transactionWaitHandle = new ManualResetEvent(false);
            var metadataWaitHandle    = new ManualResetEvent(false);

            apmServer.OnReceive += o =>
            {
                if (o is TransactionDto)
                {
                    transactionWaitHandle.Set();
                }
                else if (o is MetadataDto)
                {
                    metadataWaitHandle.Set();
                }
            };


            using var project = DotnetProject.Create(name, template, targetFramework, "--no-https");
            var environmentVariables = new Dictionary <string, string>
            {
                [EnvVarNames.ServerUrl]     = $"http://*****:*****@"\s*Now listening on:\s*(?<endpoint>http\:[^\s]*)");

                process.SubscribeLines(
                    line =>
                {
                    capturedLines.Add(line.Line);
                    var match = endpointRegex.Match(line.Line);
                    if (match.Success)
                    {
                        try
                        {
                            var endpoint = match.Groups["endpoint"].Value.Trim();
                            uri          = new UriBuilder(endpoint)
                            {
                                Path = path
                            }.Uri;
                        }
                        catch (Exception exception)
                        {
                            e = ExceptionDispatchInfo.Capture(exception);
                        }

                        startHandle.Set();
                    }
                },
                    exception => e = ExceptionDispatchInfo.Capture(exception));

                var timeout   = TimeSpan.FromMinutes(2);
                var signalled = startHandle.WaitOne(timeout);
                if (!signalled)
                {
                    throw new Exception($"Could not start dotnet project within timeout {timeout}: "
                                        + string.Join(Environment.NewLine, capturedLines));
                }

                e?.Throw();

                var client   = new HttpClient();
                var response = await client.GetAsync(uri);

                response.IsSuccessStatusCode.Should().BeTrue();

                signalled = transactionWaitHandle.WaitOne(timeout);
                if (!signalled)
                {
                    throw new Exception($"Did not receive transaction within timeout {timeout}: "
                                        + string.Join(Environment.NewLine, capturedLines)
                                        + Environment.NewLine
                                        + string.Join(Environment.NewLine, apmLogger.Lines));
                }

                apmServer.ReceivedData.Transactions.Should().HaveCount(1);

                var transaction = apmServer.ReceivedData.Transactions.First();
                transaction.Name.Should().NotBeNullOrEmpty();

                signalled = metadataWaitHandle.WaitOne(timeout);
                if (!signalled)
                {
                    throw new Exception($"Did not receive metadata within timeout {timeout}: "
                                        + string.Join(Environment.NewLine, capturedLines)
                                        + Environment.NewLine
                                        + string.Join(Environment.NewLine, apmLogger.Lines));
                }

                apmServer.ReceivedData.Metadata.Should().HaveCountGreaterOrEqualTo(1);
                var metadata = apmServer.ReceivedData.Metadata.First();
                metadata.Service.Runtime.Name.Should().NotBeNullOrEmpty();
                metadata.Service.Framework.Name.Should().Be("ASP.NET Core");
                metadata.Service.Framework.Version.Should().NotBeNullOrEmpty();
            }

            await apmServer.StopAsync();
        }
예제 #13
0
        public async Task CaptureAutoInstrumentedSpans(string targetFramework)
        {
            var apmLogger = new InMemoryBlockingLogger(Elastic.Apm.Logging.LogLevel.Error);
            var apmServer = new MockApmServer(apmLogger, nameof(CaptureAutoInstrumentedSpans));
            var port      = apmServer.FindAvailablePortToListen();

            apmServer.RunInBackground(port);

            var ignoreTopic = "ignore-topic";

            using (var profiledApplication = new ProfiledApplication("KafkaSample"))
            {
                IDictionary <string, string> environmentVariables = new Dictionary <string, string>
                {
                    ["KAFKA_HOST"]                        = _fixture.BootstrapServers,
                    ["ELASTIC_APM_SERVER_URL"]            = $"http://localhost:{port}",
                    ["ELASTIC_APM_DISABLE_METRICS"]       = "*",
                    ["ELASTIC_APM_IGNORE_MESSAGE_QUEUES"] = ignoreTopic
                };

                profiledApplication.Start(
                    targetFramework,
                    TimeSpan.FromMinutes(2),
                    environmentVariables,
                    line => _output.WriteLine(line.Line),
                    exception => _output.WriteLine($"{exception}"));
            }

            // 6 * 10 consume transactions, 14 produce transactions
            var transactions = apmServer.ReceivedData.Transactions;

            transactions.Should().HaveCount(74);

            var consumeTransactions = transactions.Where(t => t.Name.StartsWith("Kafka RECEIVE")).ToList();

            consumeTransactions.Should().HaveCount(60);

            foreach (var consumeTransaction in consumeTransactions)
            {
                var spans = apmServer.ReceivedData.Spans.Where(s => s.TransactionId == consumeTransaction.Id);
                spans.Should().HaveCount(1);
                consumeTransaction.Context.Message.Queue.Should().NotBeNull();
                consumeTransaction.Context.Message.Queue.Name.Should().NotBeNullOrEmpty();
                consumeTransaction.ParentId.Should().NotBeNull();
            }

            var produceTransactions = transactions.Where(t => !t.Name.StartsWith("Kafka RECEIVE")).ToList();

            produceTransactions.Should().HaveCount(14);

            foreach (var produceTransaction in produceTransactions)
            {
                var spans = apmServer.ReceivedData.Spans.Where(s => s.TransactionId == produceTransaction.Id).ToList();

                if (produceTransaction.Name.Contains("INVALID-TOPIC"))
                {
                    spans.Should().HaveCount(1);
                }
                // the produce transaction shouldn't have an auto instrumented publish span as topic is ignored
                else if (produceTransaction.Name.Contains(ignoreTopic))
                {
                    spans.Should().BeEmpty();
                }
                else
                {
                    spans.Should().HaveCount(10);
                }

                foreach (var span in spans)
                {
                    span.Context.Message.Should().NotBeNull();
                    span.Context.Message.Queue.Should().NotBeNull();
                    span.Context.Message.Queue.Name.Should().NotBeNullOrEmpty();
                }
            }

            await apmServer.StopAsync();
        }