diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporter.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporter.cs
index c6b32c0c5f295..ccf1b42ef334d 100644
--- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporter.cs
+++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporter.cs
@@ -13,16 +13,20 @@ namespace OpenTelemetry.Exporter.AzureMonitor
{
public class AzureMonitorTraceExporter : ActivityExporter
{
- private readonly AzureMonitorTransmitter AzureMonitorTransmitter;
+ private readonly ITransmitter Transmitter;
private readonly AzureMonitorExporterOptions options;
private readonly string instrumentationKey;
- public AzureMonitorTraceExporter(AzureMonitorExporterOptions options)
+ public AzureMonitorTraceExporter(AzureMonitorExporterOptions options) : this(options, new AzureMonitorTransmitter(options))
+ {
+ }
+
+ internal AzureMonitorTraceExporter(AzureMonitorExporterOptions options, ITransmitter transmitter)
{
this.options = options ?? throw new ArgumentNullException(nameof(options));
ConnectionString.ConnectionStringParser.GetValues(this.options.ConnectionString, out this.instrumentationKey, out _);
- this.AzureMonitorTransmitter = new AzureMonitorTransmitter(options);
+ this.Transmitter = transmitter;
}
///
@@ -34,7 +38,7 @@ public override ExportResult Export(in Batch batch)
// TODO: Handle return value, it can be converted as metrics.
// TODO: Validate CancellationToken and async pattern here.
- this.AzureMonitorTransmitter.TrackAsync(telemetryItems, false, CancellationToken.None).EnsureCompleted();
+ this.Transmitter.TrackAsync(telemetryItems, false, CancellationToken.None).EnsureCompleted();
return ExportResult.Success;
}
catch (Exception ex)
diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs
index f8da74aedd24d..9ebf50a592a81 100644
--- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs
+++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs
@@ -16,7 +16,7 @@ namespace OpenTelemetry.Exporter.AzureMonitor
///
/// This class encapsulates transmitting a collection of to the configured Ingestion Endpoint.
///
- internal class AzureMonitorTransmitter
+ internal class AzureMonitorTransmitter : ITransmitter
{
private readonly ApplicationInsightsRestClient applicationInsightsRestClient;
private readonly AzureMonitorExporterOptions options;
diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ITransmitter.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ITransmitter.cs
new file mode 100644
index 0000000000000..a88430585df0f
--- /dev/null
+++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ITransmitter.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+using OpenTelemetry.Exporter.AzureMonitor.Models;
+
+namespace OpenTelemetry.Exporter.AzureMonitor
+{
+ internal interface ITransmitter
+ {
+ ///
+ /// Sent telemetry and return the number of items Accepted.
+ ///
+ ValueTask TrackAsync(IEnumerable telemetryItems, bool async, CancellationToken cancellationToken);
+ }
+}
diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Properties/AssemblyInfo.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Properties/AssemblyInfo.cs
index e4d401bfc2169..fc00bbf242efc 100644
--- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Properties/AssemblyInfo.cs
+++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Properties/AssemblyInfo.cs
@@ -3,4 +3,8 @@
using System.Runtime.CompilerServices;
-[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.AzureMonitor.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d15ddcb29688295338af4b7686603fe614abd555e09efba8fb88ee09e1f7b1ccaeed2e8f823fa9eef3fdd60217fc012ea67d2479751a0b8c087a4185541b851bd8b16f8d91b840e51b1cb0ba6fe647997e57429265e85ef62d565db50a69ae1647d54d7bd855e4db3d8a91510e5bcbd0edfbbecaa20a7bd9ae74593daa7b11b4")]
\ No newline at end of file
+[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.AzureMonitor.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d15ddcb29688295338af4b7686603fe614abd555e09efba8fb88ee09e1f7b1ccaeed2e8f823fa9eef3fdd60217fc012ea67d2479751a0b8c087a4185541b851bd8b16f8d91b840e51b1cb0ba6fe647997e57429265e85ef62d565db50a69ae1647d54d7bd855e4db3d8a91510e5bcbd0edfbbecaa20a7bd9ae74593daa7b11b4")]
+[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.AzureMonitor.Integration.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d15ddcb29688295338af4b7686603fe614abd555e09efba8fb88ee09e1f7b1ccaeed2e8f823fa9eef3fdd60217fc012ea67d2479751a0b8c087a4185541b851bd8b16f8d91b840e51b1cb0ba6fe647997e57429265e85ef62d565db50a69ae1647d54d7bd855e4db3d8a91510e5bcbd0edfbbecaa20a7bd9ae74593daa7b11b4")]
+
+// Moq
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.Integration.Tests/OpenTelemetryTests.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.Integration.Tests/OpenTelemetryTests.cs
new file mode 100644
index 0000000000000..098a0a76bfbff
--- /dev/null
+++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.Integration.Tests/OpenTelemetryTests.cs
@@ -0,0 +1,72 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+using OpenTelemetry.Exporter.AzureMonitor.Integration.Tests.TestFramework;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace OpenTelemetry.Exporter.AzureMonitor.Integration.Tests
+{
+ public class OpenTelemetryTests : IClassFixture>
+ {
+ private readonly OpenTelemetryWebApplicationFactory factory;
+ private readonly ITestOutputHelper output;
+
+ public OpenTelemetryTests(OpenTelemetryWebApplicationFactory factory, ITestOutputHelper output)
+ {
+ this.factory = factory;
+ this.output = output;
+ }
+
+ ///
+ /// This test validates that when an app instrumented with the AzureMonitorExporter receives an HTTP request,
+ /// A TelemetryItem is created matching that request.
+ ///
+ [Fact]
+ public async Task ProofOfConcept()
+ {
+ string testValue = Guid.NewGuid().ToString();
+
+ // Arrange
+ var client = this.factory.CreateClient();
+
+ //// Act
+ var response = await client.GetAsync($"api/home/{testValue}");
+
+ // Shutdown
+ response.EnsureSuccessStatusCode();
+ Task.Delay(100).Wait(); //TODO: HOW TO REMOVE THIS WAIT?
+ this.factory.ForceFlush();
+
+ // Assert
+ Assert.True(this.factory.TelemetryItems.Any(), "test project did not capture telemetry");
+
+ PrintTelemetryItems(this.factory.TelemetryItems);
+ var item = this.factory.TelemetryItems.Single();
+ var baseData = (Models.RequestData)item.Data.BaseData;
+ Assert.True(baseData.Url.EndsWith(testValue), "it is expected that the recorded TelemetryItem matches the value of testValue.");
+ }
+
+ ///
+ /// This uses the XUnit ITestOutputHelper to print details to the output of the test run.
+ ///
+ private void PrintTelemetryItems(IEnumerable telemetryItems)
+ {
+ foreach (var item in telemetryItems)
+ {
+ this.output.WriteLine(item.Name);
+
+ if (item.Data.BaseData is Models.RequestData requestData)
+ {
+ this.output.WriteLine($"\t{requestData.Url}");
+ }
+ }
+ }
+ }
+}
diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.Integration.Tests/Properties/AssemblyInfo.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.Integration.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000000..c712fb98d8578
--- /dev/null
+++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.Integration.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Xunit;
+[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true)]
diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.Integration.Tests/TestFramework/MockTransmitter.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.Integration.Tests/TestFramework/MockTransmitter.cs
new file mode 100644
index 0000000000000..bc54cfc9ff5e7
--- /dev/null
+++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.Integration.Tests/TestFramework/MockTransmitter.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+using OpenTelemetry.Exporter.AzureMonitor.Models;
+
+namespace OpenTelemetry.Exporter.AzureMonitor.Integration.Tests.TestFramework
+{
+ public class MockTransmitter : ITransmitter
+ {
+ public ConcurrentBag TelemetryItems = new ConcurrentBag();
+
+ public ValueTask TrackAsync(IEnumerable telemetryItems, bool async, CancellationToken cancellationToken)
+ {
+ foreach (var telemetryItem in telemetryItems)
+ {
+ this.TelemetryItems.Add(telemetryItem);
+ }
+
+ return new ValueTask(Task.FromResult(telemetryItems.Count()));
+ }
+ }
+}
diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.Integration.Tests/TestFramework/OpenTelemetryWebApplicationFactory.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.Integration.Tests/TestFramework/OpenTelemetryWebApplicationFactory.cs
new file mode 100644
index 0000000000000..73ed8a614172b
--- /dev/null
+++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.Integration.Tests/TestFramework/OpenTelemetryWebApplicationFactory.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.Collections.Concurrent;
+
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.Extensions.DependencyInjection;
+
+using OpenTelemetry.Exporter.AzureMonitor.Models;
+using OpenTelemetry.Trace;
+
+namespace OpenTelemetry.Exporter.AzureMonitor.Integration.Tests.TestFramework
+{
+ ///
+ /// This class implements and will configure the for OpenTelemetry and the .
+ /// Here we mock the to capture telemetry that would have been sent to the ingestion service.
+ /// We also mock the which would have been received from the ingestion service.
+ ///
+ /// Startup class from the application to be used during this test.
+ public class OpenTelemetryWebApplicationFactory : WebApplicationFactory where TStartup : class
+ {
+ public ConcurrentBag TelemetryItems => this.Transmitter.TelemetryItems;
+
+ private const string EmptyConnectionString = "InstrumentationKey=00000000-0000-0000-0000-000000000000";
+ private readonly MockTransmitter Transmitter = new MockTransmitter();
+ private ActivityProcessor ActivityProcessor;
+
+ protected override void ConfigureWebHost(IWebHostBuilder builder)
+ {
+ this.ActivityProcessor = new BatchExportActivityProcessor(new AzureMonitorTraceExporter(
+ options: new AzureMonitorExporterOptions
+ {
+ ConnectionString = EmptyConnectionString,
+ },
+ transmitter: this.Transmitter));
+
+ builder.ConfigureServices(services => services.AddOpenTelemetryTracing((builder) => builder
+ .AddAspNetCoreInstrumentation()
+ .AddHttpClientInstrumentation()
+ .AddProcessor(this.ActivityProcessor)));
+ }
+
+ public void ForceFlush() => this.ActivityProcessor.ForceFlush();
+ }
+}
diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/AzureMonitorTraceExporterTests.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/AzureMonitorTraceExporterTests.cs
index 9768b20c5ff1b..cc37eeb7b76c6 100644
--- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/AzureMonitorTraceExporterTests.cs
+++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/AzureMonitorTraceExporterTests.cs
@@ -61,7 +61,7 @@ private void GetInternalFields(AzureMonitorTraceExporter exporter, out string ik
.ToString();
var transmitter = typeof(AzureMonitorTraceExporter)
- .GetField("AzureMonitorTransmitter", BindingFlags.Instance | BindingFlags.NonPublic)
+ .GetField("Transmitter", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(exporter);
var serviceRestClient = typeof(AzureMonitorTransmitter)