Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/Service.Tests/Configuration/Telemetry/OpenTelemetryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
// Licensed under the MIT License.

using System;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Azure.DataApiBuilder.Config.ObjectModel;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -24,6 +27,7 @@ public class OpenTelemetryTests

private const string CONFIG_WITH_TELEMETRY = "dab-open-telemetry-test-config.json";
private const string CONFIG_WITHOUT_TELEMETRY = "dab-no-open-telemetry-test-config.json";
private const string ASPNETCORE_ACTIVITY_SOURCE_NAME = "Microsoft.AspNetCore";
private static RuntimeConfig _configuration;

/// <summary>
Expand Down Expand Up @@ -88,6 +92,61 @@ public void TestOpenTelemetryServicesEnabled()
Assert.IsNotNull(meterProvider, "MeterProvider should be registered.");
}

/// <summary>
/// Tests that an inbound HTTP request produces a recorded server span when Open Telemetry is enabled,
/// and that this span continues the trace context received in the W3C traceparent header.
/// The listener below never samples by itself, so the span can only be recorded because
/// the OpenTelemetry TracerProvider subscribes to the ASP.NET Core activity source.
/// </summary>
[TestMethod]
public async Task TestOpenTelemetryRecordsInboundHttpRequestSpan()
{
// Arrange
SetUpTelemetryInConfig(CONFIG_WITH_TELEMETRY, true, "http://localhost:4317", "key=key", OtlpExportProtocol.Grpc);

ActivityTraceId incomingTraceId = ActivityTraceId.CreateRandom();
ActivitySpanId incomingParentSpanId = ActivitySpanId.CreateRandom();
TaskCompletionSource<Activity> recordedServerActivity = new(TaskCreationOptions.RunContinuationsAsynchronously);
using ActivityListener listener = new()
{
ShouldListenTo = source => source.Name == ASPNETCORE_ACTIVITY_SOURCE_NAME,
Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.None,
ActivityStopped = activity =>
{
if (activity.TraceId == incomingTraceId)
{
recordedServerActivity.TrySetResult(activity);
}
}
};
ActivitySource.AddActivityListener(listener);

string[] args = new[]
{
$"--ConfigFileName={CONFIG_WITH_TELEMETRY}"
};
using TestServer server = new(Program.CreateWebHostBuilder(args));
Assert.IsNotNull(server.Services.GetService<TracerProvider>(), "TracerProvider should be registered.");
using HttpClient client = server.CreateClient();

using HttpRequestMessage request = new(HttpMethod.Get, "/");
request.Headers.Add("traceparent", $"00-{incomingTraceId.ToHexString()}-{incomingParentSpanId.ToHexString()}-01");

// Act
using HttpResponseMessage response = await client.SendAsync(request);

// Assert
// The server activity is stopped once the request pipeline completes, which can happen after the response is returned.
Task completedTask = await Task.WhenAny(recordedServerActivity.Task, Task.Delay(TimeSpan.FromSeconds(10)));
Assert.AreSame(recordedServerActivity.Task, completedTask, "An inbound HTTP request should produce a server span.");

Activity serverActivity = await recordedServerActivity.Task;
Assert.IsTrue(serverActivity.Recorded, "The server span should be recorded.");
Assert.AreEqual(ActivityKind.Server, serverActivity.Kind, "The span should be a server span.");
Assert.AreEqual(incomingTraceId, serverActivity.TraceId, "The server span should continue the incoming trace.");
Assert.AreEqual(incomingParentSpanId, serverActivity.ParentSpanId, "The server span should be parented to the incoming span.");
}

/// <summary>
/// Tests if the services are correctly disabled for Open Telemetry.
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions src/Service/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ public void ConfigureServices(IServiceCollection services)
.WithTracing(tracing =>
{
tracing.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(runtimeConfig.Runtime.Telemetry.OpenTelemetry.ServiceName!))
// Creates a server span for every inbound HTTP request (REST, GraphQL, MCP, health)
// and continues the incoming W3C traceparent, so DAB spans are parented correctly.
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
// TODO: should we also add FusionCache traces?
// To do so we just need to add the package ZiggyCreatures.FusionCache.OpenTelemetry and call
Expand Down