private TelemetryItem RunActivityTest(Action <ActivitySource> testScenario)
        {
            // SETUP
            var ActivitySourceName = $"{nameof(TelemetryItemTests)}.{nameof(RunActivityTest)}";

            using var activitySource = new ActivitySource(ActivitySourceName);

            var mockTransmitter = new MockTransmitter();
            var processor       = new BatchActivityExportProcessor(new AzureMonitorTraceExporter(mockTransmitter));

            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                                       .SetSampler(new AlwaysOnSampler())
                                       .AddSource(ActivitySourceName)
                                       .AddProcessor(processor)
                                       .Build();

            // ACT
            testScenario(activitySource);

            // CLEANUP
            processor.ForceFlush();

            Assert.True(mockTransmitter.TelemetryItems.Any(), "test project did not capture telemetry");
            return(mockTransmitter.TelemetryItems.Single());
        }
        public void CheckShutdownExport(int timeout)
        {
            var exportedItems = new List <Activity>();

            using var exporter  = new InMemoryExporter <Activity>(exportedItems);
            using var processor = new BatchActivityExportProcessor(
                      exporter,
                      maxQueueSize: 3,
                      maxExportBatchSize: 3,
                      exporterTimeoutMilliseconds: 30000);

            var activity = new Activity("start");

            activity.ActivityTraceFlags = ActivityTraceFlags.Recorded;

            processor.OnEnd(activity);
            processor.Shutdown(timeout);

            if (timeout == 0)
            {
                // Shutdown(0) will trigger flush and return immediately, so let's sleep for a while
                Thread.Sleep(1_000);
            }

            Assert.Single(exportedItems);

            Assert.Equal(1, processor.ProcessedCount);
            Assert.Equal(1, processor.ReceivedCount);
            Assert.Equal(0, processor.DroppedCount);
        }
        public void CheckForceFlushWithInvalidTimeout()
        {
            var exportedItems = new List <Activity>();

            using var exporter  = new InMemoryExporter <Activity>(exportedItems);
            using var processor = new BatchActivityExportProcessor(exporter, maxQueueSize: 2, maxExportBatchSize: 1);
            Assert.Throws <ArgumentOutOfRangeException>(() => processor.ForceFlush(-2));
        }
        public void VerifyLoggerWithActivity()
        {
            // SETUP
            var ActivitySourceName = $"{nameof(TelemetryItemTests)}.{nameof(VerifyLoggerWithActivity)}";

            using var activitySource = new ActivitySource(ActivitySourceName);

            var mockTransmitter = new MockTransmitter();

            var processor1 = new BatchActivityExportProcessor(new AzureMonitorTraceExporter(mockTransmitter));

            var processor2 = new BatchLogRecordExportProcessor(new AzureMonitorLogExporter(mockTransmitter));

            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                                       .SetSampler(new AlwaysOnSampler())
                                       .AddSource(ActivitySourceName)
                                       .AddProcessor(processor1)
                                       .Build();

            var serviceCollection = new ServiceCollection().AddLogging(builder =>
            {
                builder.SetMinimumLevel(LogLevel.Trace)
                .AddOpenTelemetry(options => options
                                  .AddProcessor(processor2));
            });

            using var serviceProvider = serviceCollection.BuildServiceProvider();
            var logger = serviceProvider.GetRequiredService <ILogger <TelemetryItemTests> >();

            // ACT
            using (var activity = activitySource.StartActivity(name: "test activity", kind: ActivityKind.Server))
            {
                activity.SetTag("message", "hello activity!");

                logger.LogWarning("hello ilogger");
            }

            // CLEANUP
            processor1.ForceFlush();
            processor2.ForceFlush();

            // VERIFY
            Assert.True(mockTransmitter.TelemetryItems.Any(), "test project did not capture telemetry");
            Assert.Equal(2, mockTransmitter.TelemetryItems.Count);

            var logTelemetry      = mockTransmitter.TelemetryItems.Single(x => x.Name == "Message");
            var activityTelemetry = mockTransmitter.TelemetryItems.Single(x => x.Name == "Request");

            var activityId  = ((RequestData)activityTelemetry.Data.BaseData).Id;
            var operationId = activityTelemetry.Tags["ai.operation.id"];

            Assert.Equal(activityId, logTelemetry.Tags["ai.operation.parentId"]);
            Assert.Equal(operationId, logTelemetry.Tags["ai.operation.id"]);
        }
Example #5
0
        public void CheckForceFlushExport(int timeout)
        {
            var exportedItems = new List <Activity>();

            using var exporter  = new InMemoryExporter <Activity>(exportedItems);
            using var processor = new BatchActivityExportProcessor(
                      exporter,
                      maxQueueSize: 3,
                      maxExportBatchSize: 3,
                      exporterTimeoutMilliseconds: 30000);

            var activity1 = new Activity("start1")
            {
                ActivityTraceFlags = ActivityTraceFlags.Recorded,
            };

            var activity2 = new Activity("start2")
            {
                ActivityTraceFlags = ActivityTraceFlags.Recorded,
            };

            processor.OnEnd(activity1);
            processor.OnEnd(activity2);

            Assert.Equal(0, processor.ProcessedCount);

            // waiting to see if time is triggering the exporter
            Thread.Sleep(1_000);
            Assert.Empty(exportedItems);

            // forcing flush
            processor.ForceFlush(timeout);

            if (timeout == 0)
            {
                // ForceFlush(0) will trigger flush and return immediately, so let's sleep for a while
                Thread.Sleep(1_000);
            }

            Assert.Equal(2, exportedItems.Count);

            Assert.Equal(2, processor.ProcessedCount);
            Assert.Equal(2, processor.ReceivedCount);
            Assert.Equal(0, processor.DroppedCount);
        }
        public void CheckExportDrainsBatchOnFailure()
        {
            using var exporter  = new InMemoryExporter <Activity>(null);
            using var processor = new BatchActivityExportProcessor(
                      exporter,
                      maxQueueSize: 3,
                      maxExportBatchSize: 3);

            var activity = new Activity("start");

            activity.ActivityTraceFlags = ActivityTraceFlags.Recorded;

            processor.OnEnd(activity);
            processor.OnEnd(activity);
            processor.OnEnd(activity);
            processor.Shutdown();

            Assert.Equal(3, processor.ProcessedCount); // Verify batch was drained even though nothing was exported.
        }
        public void CheckExportForRecordingButNotSampledActivity()
        {
            var exportedItems = new List <Activity>();

            using var exporter  = new InMemoryExporter <Activity>(exportedItems);
            using var processor = new BatchActivityExportProcessor(
                      exporter,
                      maxQueueSize: 1,
                      maxExportBatchSize: 1);

            var activity = new Activity("start");

            activity.ActivityTraceFlags = ActivityTraceFlags.None;

            processor.OnEnd(activity);
            processor.Shutdown();

            Assert.Empty(exportedItems);
            Assert.Equal(0, processor.ProcessedCount);
        }
        public void StackdriverExporter_TraceClientThrows_ExportResultFailure()
        {
            Exception    exception          = null;
            ExportResult result             = ExportResult.Success;
            const string ActivitySourceName = "stackdriver.test";
            var          source             = new ActivitySource(ActivitySourceName);
            var          traceClientMock    = new Mock <TraceServiceClient>(MockBehavior.Strict);

            traceClientMock.Setup(x =>
                                  x.BatchWriteSpans(It.IsAny <BatchWriteSpansRequest>(), It.IsAny <CallSettings>()))
            .Throws(new RpcException(Status.DefaultCancelled))
            .Verifiable($"{nameof(TraceServiceClient.BatchWriteSpans)} was never called");
            var activityExporter = new StackdriverTraceExporter("test", traceClientMock.Object);
            var testExporter     = new TestExporter <Activity>(RunTest);

            var processor = new BatchActivityExportProcessor(testExporter);

            for (int i = 0; i < 10; i++)
            {
                using Activity activity = source.StartActivity("Test Activity");
                processor.OnEnd(activity);
            }

            processor.Shutdown();

            void RunTest(Batch <Activity> batch)
            {
                exception = Record.Exception(() =>
                {
                    result = activityExporter.Export(batch);
                });
            }

            Assert.Null(exception);
            Assert.StrictEqual(ExportResult.Failure, result);
            traceClientMock.VerifyAll();
        }
        public void SendExportRequest_ExportTraceServiceRequest_SendsCorrectHttpRequest(bool includeServiceNameInResource)
        {
            // Arrange
            var evenTags = new[] { new KeyValuePair <string, object>("k0", "v0") };
            var oddTags  = new[] { new KeyValuePair <string, object>("k1", "v1") };
            var sources  = new[]
            {
                new ActivitySource("even", "2.4.6"),
                new ActivitySource("odd", "1.3.5"),
            };
            var header1 = new { Name = "hdr1", Value = "val1" };
            var header2 = new { Name = "hdr2", Value = "val2" };

            var options = new OtlpExporterOptions
            {
                Endpoint = new Uri("http://localhost:4317"),
                Headers  = $"{header1.Name}={header1.Value}, {header2.Name} = {header2.Value}",
            };

            var httpHandlerMock = new Mock <HttpMessageHandler>();

            HttpRequestMessage httpRequest = null;
            var httpRequestContent         = Array.Empty <byte>();

            httpHandlerMock.Protected()
#if NET5_0_OR_GREATER
            .Setup <HttpResponseMessage>("Send", ItExpr.IsAny <HttpRequestMessage>(), ItExpr.IsAny <CancellationToken>())
            .Returns((HttpRequestMessage request, CancellationToken token) =>
            {
                return(new HttpResponseMessage());
            })
            .Callback <HttpRequestMessage, CancellationToken>((r, ct) =>
            {
                httpRequest = r;

                // We have to capture content as it can't be accessed after request is disposed inside of SendExportRequest method
                httpRequestContent = r.Content.ReadAsByteArrayAsync()?.Result;
            })
#else
            .Setup <Task <HttpResponseMessage> >("SendAsync", ItExpr.IsAny <HttpRequestMessage>(), ItExpr.IsAny <CancellationToken>())
            .ReturnsAsync((HttpRequestMessage request, CancellationToken token) =>
            {
                return(new HttpResponseMessage());
            })
            .Callback <HttpRequestMessage, CancellationToken>(async(r, ct) =>
            {
                httpRequest = r;

                // We have to capture content as it can't be accessed after request is disposed inside of SendExportRequest method
                httpRequestContent = await r.Content.ReadAsByteArrayAsync();
            })
#endif
            .Verifiable();

            var exportClient = new OtlpHttpTraceExportClient(options, new HttpClient(httpHandlerMock.Object));

            var resourceBuilder = ResourceBuilder.CreateEmpty();
            if (includeServiceNameInResource)
            {
                resourceBuilder.AddAttributes(
                    new List <KeyValuePair <string, object> >
                {
                    new KeyValuePair <string, object>(ResourceSemanticConventions.AttributeServiceName, "service_name"),
                    new KeyValuePair <string, object>(ResourceSemanticConventions.AttributeServiceNamespace, "ns_1"),
                });
            }

            var builder = Sdk.CreateTracerProviderBuilder()
                          .SetResourceBuilder(resourceBuilder)
                          .AddSource(sources[0].Name)
                          .AddSource(sources[1].Name);

            using var openTelemetrySdk = builder.Build();

            var       processor  = new BatchActivityExportProcessor(new TestExporter <Activity>(RunTest));
            const int numOfSpans = 10;
            bool      isEven;
            for (var i = 0; i < numOfSpans; i++)
            {
                isEven = i % 2 == 0;
                var source       = sources[i % 2];
                var activityKind = isEven ? ActivityKind.Client : ActivityKind.Server;
                var activityTags = isEven ? evenTags : oddTags;

                using Activity activity = source.StartActivity($"span-{i}", activityKind, parentContext: default, activityTags);
                processor.OnEnd(activity);
            }

            processor.Shutdown();

            void RunTest(Batch <Activity> batch)
            {
                var request = new OtlpCollector.ExportTraceServiceRequest();

                request.AddBatch(resourceBuilder.Build().ToOtlpResource(), batch);

                // Act
                var result = exportClient.SendExportRequest(request);

                // Assert
                Assert.True(result);
                Assert.NotNull(httpRequest);
                Assert.Equal(HttpMethod.Post, httpRequest.Method);
                Assert.Equal("http://localhost:4317/v1/traces", httpRequest.RequestUri.AbsoluteUri);
                Assert.Equal(2, httpRequest.Headers.Count());
                Assert.Contains(httpRequest.Headers, h => h.Key == header1.Name && h.Value.First() == header1.Value);
                Assert.Contains(httpRequest.Headers, h => h.Key == header2.Name && h.Value.First() == header2.Value);

                Assert.NotNull(httpRequest.Content);
                Assert.IsType <OtlpHttpTraceExportClient.ExportRequestContent>(httpRequest.Content);
                Assert.Contains(httpRequest.Content.Headers, h => h.Key == "Content-Type" && h.Value.First() == OtlpHttpTraceExportClient.MediaContentType);

                var exportTraceRequest = OtlpCollector.ExportTraceServiceRequest.Parser.ParseFrom(httpRequestContent);

                Assert.NotNull(exportTraceRequest);
                Assert.Single(exportTraceRequest.ResourceSpans);

                var resourceSpan = exportTraceRequest.ResourceSpans.First();

                if (includeServiceNameInResource)
                {
                    Assert.Contains(resourceSpan.Resource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.StringValue == "service_name");
                    Assert.Contains(resourceSpan.Resource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceNamespace && kvp.Value.StringValue == "ns_1");
                }
                else
                {
                    Assert.Contains(resourceSpan.Resource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.ToString().Contains("unknown_service:"));
                }
            }
        }
        public void ToOtlpResourceSpansTest(bool addResource)
        {
            var evenTags = new[] { new KeyValuePair <string, object>("k0", "v0") };
            var oddTags  = new[] { new KeyValuePair <string, object>("k1", "v1") };
            var sources  = new[]
            {
                new ActivitySource("even", "2.4.6"),
                new ActivitySource("odd", "1.3.5"),
            };

            using var exporter = new OtlpTraceExporter(
                      new OtlpExporterOptions(),
                      new NoopTraceServiceClient());

            if (addResource)
            {
                exporter.SetResource(
                    ResourceBuilder.CreateEmpty().AddAttributes(
                        new List <KeyValuePair <string, object> >
                {
                    new KeyValuePair <string, object>(Resources.ResourceSemanticConventions.AttributeServiceName, "service-name"),
                    new KeyValuePair <string, object>(Resources.ResourceSemanticConventions.AttributeServiceNamespace, "ns1"),
                }).Build());
            }
            else
            {
                exporter.SetResource(Resources.Resource.Empty);
            }

            var builder = Sdk.CreateTracerProviderBuilder()
                          .AddSource(sources[0].Name)
                          .AddSource(sources[1].Name);

            using var openTelemetrySdk = builder.Build();

            var       processor  = new BatchActivityExportProcessor(new TestExporter <Activity>(RunTest));
            const int numOfSpans = 10;
            bool      isEven;

            for (var i = 0; i < numOfSpans; i++)
            {
                isEven = i % 2 == 0;
                var source       = sources[i % 2];
                var activityKind = isEven ? ActivityKind.Client : ActivityKind.Server;
                var activityTags = isEven ? evenTags : oddTags;

                using Activity activity = source.StartActivity($"span-{i}", activityKind, parentContext: default, activityTags);
                processor.OnEnd(activity);
            }

            processor.Shutdown();

            void RunTest(Batch <Activity> batch)
            {
                var request = new OtlpCollector.ExportTraceServiceRequest();

                request.AddBatch(exporter.ProcessResource, batch);

                Assert.Single(request.ResourceSpans);
                var oltpResource = request.ResourceSpans.First().Resource;

                if (addResource)
                {
                    Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == Resources.ResourceSemanticConventions.AttributeServiceName && kvp.Value.StringValue == "service-name");
                    Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == Resources.ResourceSemanticConventions.AttributeServiceNamespace && kvp.Value.StringValue == "ns1");
                }
                else
                {
                    Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == Resources.ResourceSemanticConventions.AttributeServiceName && kvp.Value.ToString().Contains("unknown_service:"));
                }

                foreach (var instrumentationLibrarySpans in request.ResourceSpans.First().InstrumentationLibrarySpans)
                {
                    Assert.Equal(numOfSpans / 2, instrumentationLibrarySpans.Spans.Count);
                    Assert.NotNull(instrumentationLibrarySpans.InstrumentationLibrary);

                    var expectedSpanNames = new List <string>();
                    var start             = instrumentationLibrarySpans.InstrumentationLibrary.Name == "even" ? 0 : 1;
                    for (var i = start; i < numOfSpans; i += 2)
                    {
                        expectedSpanNames.Add($"span-{i}");
                    }

                    var otlpSpans = instrumentationLibrarySpans.Spans;
                    Assert.Equal(expectedSpanNames.Count, otlpSpans.Count);

                    var kv0 = new OtlpCommon.KeyValue {
                        Key = "k0", Value = new OtlpCommon.AnyValue {
                            StringValue = "v0"
                        }
                    };
                    var kv1 = new OtlpCommon.KeyValue {
                        Key = "k1", Value = new OtlpCommon.AnyValue {
                            StringValue = "v1"
                        }
                    };

                    var expectedTag = instrumentationLibrarySpans.InstrumentationLibrary.Name == "even"
                        ? kv0
                        : kv1;

                    foreach (var otlpSpan in otlpSpans)
                    {
                        Assert.Contains(otlpSpan.Name, expectedSpanNames);
                        Assert.Contains(expectedTag, otlpSpan.Attributes);
                    }
                }
            }
        }