Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AzureMonitorDistro] Add field validation to Integration Tests (part2) #44538

Merged
merged 14 commits into from
Jun 14, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

<ProjectReference Include="$(AzureCoreTestFramework)" />
<ProjectReference Include="..\..\src\Azure.Monitor.OpenTelemetry.AspNetCore.csproj" />
<ProjectReference Include="..\..\..\Azure.Monitor.OpenTelemetry.Exporter\src\Azure.Monitor.OpenTelemetry.Exporter.csproj" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NUnit.Framework;
using OpenTelemetry;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
Expand All @@ -25,24 +26,23 @@ namespace Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests
{
public class DistroWebAppLiveTests : RecordedTestBase<AzureMonitorTestEnvironment>
{
private const string TestServerUrl = "http://localhost:9998/";

// DEVELOPER TIP: Change roleName to something unique when working locally (Example "Test##") to easily find your records.
// Can search for all records in the portal via Log Analytics using this query: Union * | where AppRoleName == 'Test##'
private const string RoleName = nameof(DistroWebAppLiveTests);
private const string TestServerPort = "9998";
private const string TestServerTarget = $"localhost:{TestServerPort}";
private const string TestServerUrl = $"http://{TestServerTarget}/";

private const string LogMessage = "Message via ILogger";

private LogsQueryClient? _logsQueryClient = null;

// DEVELOPER TIP: Can pass RecordedTestMode.Live into the base ctor to run this test with a live resource.
// DEVELOPER TIP: Can pass RecordedTestMode.Record into the base ctor to re-record the SessionRecords.
public DistroWebAppLiveTests(bool isAsync) : base(isAsync) { }
public DistroWebAppLiveTests(bool isAsync) : base(isAsync, RecordedTestMode.Playback) { }

[RecordedTest]
[SyncOnly] // This test cannot run concurrently with another test because OTel instruments the process and will cause side effects.
public async Task VerifyDistro()
{
var testStartTimeStamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffffffZ");
Console.WriteLine($"Integration test '{nameof(VerifyDistro)}' running in mode '{TestEnvironment.Mode}'");

// DEVELOPER TIP: This test implicitly checks for telemetry within the last 30 minutes.
Expand All @@ -66,9 +66,14 @@ public async Task VerifyDistro()
_logsQueryClient.SetQueryWorkSpaceId(TestEnvironment.WorkspaceId);

// SETUP WEBAPPLICATION WITH OPENTELEMETRY
string serviceName = "TestName", serviceNamespace = "TestNamespace", serviceInstance = "TestInstance", serviceVersion = "TestVersion";
string roleName = $"[{serviceNamespace}]/{serviceName}";
var resourceAttributes = new Dictionary<string, object>
{
{ "service.name", RoleName },
{ "service.name", serviceName },
{ "service.namespace", serviceNamespace },
{ "service.instance.id", serviceInstance },
{ "service.version", serviceVersion }
};

var resourceBuilder = ResourceBuilder.CreateDefault();
Expand All @@ -81,6 +86,7 @@ public async Task VerifyDistro()
{
options.SetResourceBuilder(resourceBuilder);
});
builder.Services.ConfigureOpenTelemetryTracerProvider((sp, builder) => builder.AddProcessor(new ActivityEnrichingProcessor()));
builder.Services.AddOpenTelemetry()
.ConfigureResource(x => x.AddAttributes(resourceAttributes))
.UseAzureMonitor(options =>
Expand Down Expand Up @@ -119,52 +125,109 @@ public async Task VerifyDistro()
// NOTE: The following queries are using the LogAnalytics schema.
await QueryAndVerifyDependency(
description: "Dependency for invoking HttpClient, from testhost",
query: $"AppDependencies | where Data == '{TestServerUrl}' | where AppRoleName == '{RoleName}' | top 1 by TimeGenerated",
//query: $"AppDependencies | where Data == '{TestServerUrl}' | where AppRoleName == '{roleName}' | where TimeGenerated >= datetime({ testStartTimeStamp}) | top 1 by TimeGenerated",
query: $"AppDependencies | where Data == '{TestServerUrl}' | where AppRoleName == '{roleName}' | top 1 by TimeGenerated",
expectedAppDependency: new ExpectedAppDependency
{
Target = TestServerTarget,
DependencyType = "HTTP",
Name = "GET /",
Data = TestServerUrl,
AppRoleName = RoleName,
Success = "True",
ResultCode = "200",
AppVersion = serviceVersion,
AppRoleName = roleName,
ClientIP = "0.0.0.0",
Type = "AppDependencies",
UserAuthenticatedId = "TestAuthenticatedUserId",
//AppRoleInstance = serviceInstance, // TODO: Investigate failure
Properties = new List<KeyValuePair<string, string>>
{
new("_MS.ProcessedByMetricExtractors", "(Name: X,Ver:'1.1')"),
new("CustomProperty1", "Value1"),
},
});

await QueryAndVerifyRequest(
description: "RequestTelemetry, from WebApp",
query: $"AppRequests | where Url == '{TestServerUrl}' | where AppRoleName == '{RoleName}' | top 1 by TimeGenerated",
//query: $"AppRequests | where Url == '{TestServerUrl}' | where AppRoleName == '{roleName}' | where TimeGenerated >= datetime({testStartTimeStamp}) | top 1 by TimeGenerated",
query: $"AppRequests | where Url == '{TestServerUrl}' | where AppRoleName == '{roleName}' | top 1 by TimeGenerated",
expectedAppRequest: new ExpectedAppRequest
{
Url = TestServerUrl,
AppRoleName = RoleName,
AppRoleName = roleName,
Name = "GET /",
Success = "True",
ResultCode = "200",
OperationName = "GET /",
AppVersion = serviceVersion,
ClientIP = "0.0.0.0",
Type = "AppRequests",
UserAuthenticatedId = "TestAuthenticatedUserId",
//AppRoleInstance = serviceInstance, // TODO: Investigate failure
Properties = new List<KeyValuePair<string, string>>
{
new("_MS.ProcessedByMetricExtractors", "(Name: X,Ver:'1.1')"),
new("CustomProperty1", "Value1"),
},
});

await QueryAndVerifyMetric(
description: "Metric for outgoing request, from testhost",
query: $"AppMetrics | where Name == 'http.client.request.duration' | where AppRoleName == '{RoleName}' | where Properties.['server.address'] == 'localhost' | top 1 by TimeGenerated",
//query: $"AppMetrics | where Name == 'http.client.request.duration' | where AppRoleName == '{roleName}' | where Properties.['server.address'] == 'localhost' | where TimeGenerated >= datetime({testStartTimeStamp}) | top 1 by TimeGenerated",
query: $"AppMetrics | where Name == 'http.client.request.duration' | where AppRoleName == '{roleName}' | where Properties.['server.address'] == 'localhost' | top 1 by TimeGenerated",
expectedAppMetric: new ExpectedAppMetric
{
Name = "http.client.request.duration",
AppRoleName = RoleName,
AppRoleName = roleName,
AppVersion = serviceVersion,
Type = "AppMetrics",
//AppRoleInstance = serviceInstance, // TODO: Investigate failure
Properties = new List<KeyValuePair<string, string>>
{
new("http.request.method", "GET"),
new("http.response.status_code", "200"),
new("network.protocol.version", "1.1"),
new("server.address", "localhost"),
new("server.port", "9998"),
new("url.scheme", "http"),
},
});

await QueryAndVerifyMetric(
description: "Metric for incoming request, from WebApp",
query: $"AppMetrics | where Name == 'http.server.request.duration' | where AppRoleName == '{RoleName}' | top 1 by TimeGenerated",
//query: $"AppMetrics | where Name == 'http.server.request.duration' | where AppRoleName == '{roleName}' | where TimeGenerated >= datetime({testStartTimeStamp}) | top 1 by TimeGenerated",
query: $"AppMetrics | where Name == 'http.server.request.duration' | where AppRoleName == '{roleName}' | top 1 by TimeGenerated",
expectedAppMetric: new ExpectedAppMetric
{
Name = "http.server.request.duration",
AppRoleName = RoleName,
Properties = new(),
AppRoleName = roleName,
AppVersion = serviceVersion,
Type = "AppMetrics",
//AppRoleInstance = serviceInstance, // TODO: Investigate failure
Properties = new List<KeyValuePair<string, string>>
{
new("http.request.method", "GET"),
new("http.response.status_code", "200"),
new("http.route", "/"),
new("network.protocol.version", "1.1"),
new("url.scheme", "http")
}
});

await QueryAndVerifyTrace(
description: "ILogger LogInformation, from WebApp",
query: $"AppTraces | where Message == '{LogMessage}' | where AppRoleName == '{RoleName}' | top 1 by TimeGenerated",
//query: $"AppTraces | where Message == '{LogMessage}' | where AppRoleName == '{roleName}' | where TimeGenerated >= datetime({testStartTimeStamp}) | top 1 by TimeGenerated",
query: $"AppTraces | where Message == '{LogMessage}' | where AppRoleName == '{roleName}' | top 1 by TimeGenerated",
expectedAppTrace: new ExpectedAppTrace
{
Message = LogMessage,
AppRoleName = RoleName,
SeverityLevel = "1",
AppRoleName = roleName,
AppVersion = serviceVersion,
ClientIP = "0.0.0.0",
Type = "AppTraces",
//AppRoleInstance = serviceInstance, // TODO: Investigate failure
});
}

Expand All @@ -191,6 +254,15 @@ private async Task QueryAndVerifyTrace(string description, string query, Expecte
LogsTable? logsTable = await _logsQueryClient!.QueryTelemetryAsync(description, query);
ValidateExpectedTelemetry(description, logsTable, expectedAppTrace);
}

public class ActivityEnrichingProcessor : BaseProcessor<Activity>
{
public override void OnEnd(Activity activity)
{
activity.SetTag("CustomProperty1", "Value1");
activity.SetTag("enduser.id", "TestAuthenticatedUserId");
}
}
}
}
#endif
Loading