Skip to content

Commit

Permalink
[AzureMonitorDistro] Add field validation to Integration Tests (part2) (
Browse files Browse the repository at this point in the history
#44538)

* saving work in progress. finished with Dependency

* remove Live

* more fields

* metrics

* logs

* add service.instance. add timestamp to query

* update

* test fix for ci

* update recording

* cleanup

* update instance id

* comment

* comment

* remove unused fields
  • Loading branch information
TimothyMothra committed Jun 14, 2024
1 parent a14917d commit 717ba30
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 117 deletions.
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,11 +26,9 @@ 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";

Expand All @@ -43,16 +42,9 @@ public DistroWebAppLiveTests(bool isAsync) : base(isAsync) { }
[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.
// When working locally, this has the benefit of "priming" telemetry so that additional runs can complete faster without waiting for ingestion.
// This can negatively impact the test results if you are debugging locally and making changes to the telemetry.
// To mitigate this, you can include a timestamp in the query to only check for telemetry created since this test started.
// IMPORTANT: we cannot include timestamps in the Recorded test because it breaks queries during playback.
// C#: var testStartTimeStamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffffffZ");
// QUERY: | where TimeGenerated >= datetime({ testStartTimeStamp})

// SETUP TELEMETRY CLIENT (FOR QUERYING LOG ANALYTICS)
_logsQueryClient = InstrumentClient(new LogsQueryClient(
TestEnvironment.LogsEndpoint,
Expand All @@ -66,9 +58,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,13 +78,15 @@ 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 =>
{
options.EnableLiveMetrics = false;
options.ConnectionString = TestEnvironment.ConnectionString;
});
})
// Custom resources must be added AFTER AzureMonitor to override the included ResourceDetectors.
.ConfigureResource(x => x.AddAttributes(resourceAttributes));

var app = builder.Build();
app.MapGet("/", () =>
Expand Down Expand Up @@ -117,54 +116,120 @@ public async Task VerifyDistro()

// ASSERT
// NOTE: The following queries are using the LogAnalytics schema.

// DEVELOPER TIP: This test implicitly checks for telemetry within the last 30 minutes.
// When working locally, this has the benefit of "priming" telemetry so that additional runs can complete faster without waiting for ingestion.
// This can negatively impact the test results if you are debugging locally and making changes to the telemetry.
// To mitigate this, you can include a timestamp in the query to only check for telemetry created since this test started.
// IMPORTANT: we cannot include timestamps in the Recorded test because queries won't match during playback.
// C#: var testStartTimeStamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffffffZ");
// QUERY: | where TimeGenerated >= datetime({testStartTimeStamp})

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,
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,
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,
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,
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,
});
}

Expand All @@ -191,6 +256,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

0 comments on commit 717ba30

Please sign in to comment.