diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests.csproj b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests.csproj index 4d7ab15af3f60..8a2c6f076a4e8 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests.csproj +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests.csproj @@ -11,6 +11,7 @@ + diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests/DistroWebAppLiveTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests/DistroWebAppLiveTests.cs index b27be2ad51cb0..6c7123d660994 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests/DistroWebAppLiveTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests/DistroWebAppLiveTests.cs @@ -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; @@ -25,11 +26,9 @@ namespace Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests { public class DistroWebAppLiveTests : RecordedTestBase { - 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"; @@ -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, @@ -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 { - { "service.name", RoleName }, + { "service.name", serviceName }, + { "service.namespace", serviceNamespace }, + { "service.instance.id", serviceInstance }, + { "service.version", serviceVersion } }; var resourceBuilder = ResourceBuilder.CreateDefault(); @@ -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("/", () => @@ -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> + { + 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> + { + 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> { + 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> + { + 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, }); } @@ -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 + { + public override void OnEnd(Activity activity) + { + activity.SetTag("CustomProperty1", "Value1"); + activity.SetTag("enduser.id", "TestAuthenticatedUserId"); + } + } } } #endif diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests/SessionRecords/DistroWebAppLiveTests/VerifyDistro.json b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests/SessionRecords/DistroWebAppLiveTests/VerifyDistro.json index aa039e05837a1..54c895dec01f7 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests/SessionRecords/DistroWebAppLiveTests/VerifyDistro.json +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests/SessionRecords/DistroWebAppLiveTests/VerifyDistro.json @@ -6,15 +6,15 @@ "RequestHeaders": { "Accept": "application/json", "Authorization": "Sanitized", - "Content-Length": "179", + "Content-Length": "182", "Content-Type": "application/json", - "traceparent": "00-c4516adafd33bcee5da05c3bcb37dd40-938a217833252e6f-00", + "traceparent": "00-bd93e9fb2d91a5a03fabf9633be9ea5e-12ca8fcea0ac5283-00", "User-Agent": "azsdk-net-Monitor.Query/1.1.0 (.NET 7.0.20; Microsoft Windows 10.0.22631)", "x-ms-client-request-id": "Sanitized", "x-ms-return-client-request-id": "true" }, "RequestBody": { - "query": "AppDependencies | where Data == 'http://localhost:9998/' | where AppRoleName == 'DistroWebAppLiveTests' | top 1 by TimeGenerated", + "query": "AppDependencies | where Data == 'http://localhost:9998/' | where AppRoleName == '[TestNamespace]/TestName' | top 1 by TimeGenerated", "timespan": "PT30M" }, "StatusCode": 200, @@ -22,12 +22,12 @@ "Access-Control-Allow-Origin": "*", "Access-Control-Expose-Headers": "Retry-After,Age,WWW-Authenticate,x-resource-identities,x-ms-status-location", "Connection": "keep-alive", - "Content-Length": "2423", + "Content-Length": "2475", "Content-Type": "application/json; charset=utf-8", - "Date": "Fri, 07 Jun 2024 21:44:57 GMT", + "Date": "Thu, 13 Jun 2024 20:46:45 GMT", "Strict-Transport-Security": "max-age=15724800; includeSubDomains", "Vary": "Accept-Encoding", - "Via": "1.1 draft-oms-84fccbf47-9zf5l", + "Via": "1.1 draft-oms-7b5b6f666d-hcnc8", "X-Content-Type-Options": "nosniff" }, "ResponseBody": { @@ -203,29 +203,29 @@ "rows": [ [ "d49041ed-21aa-42c5-a6f3-6d60bb93d63c", - "2024-06-07T21:44:23.1556457Z", - "2f64853161967288", + "2024-06-13T20:41:12.3196552Z", + "f95c51aa6574bae5", "localhost:9998", "HTTP", "GET /", "http://localhost:9998/", true, "200", - 81.8636, + 68.1025, "<250ms", - "{\"_MS.ProcessedByMetricExtractors\":\"(Name: X,Ver:'1.1')\"}", + "{\"CustomProperty1\":\"Value1\",\"_MS.ProcessedByMetricExtractors\":\"(Name: X,Ver:'1.1')\"}", null, "", - "2f5dc72a16261a3a9e74e74ca0f3b4bf", - "2f5dc72a16261a3a9e74e74ca0f3b4bf", + "986a2a0d5230df5f38c79666b260ea1b", + "986a2a0d5230df5f38c79666b260ea1b", "", "", "", + "TestAuthenticatedUserId", "", - "", - "", - "DistroWebAppLiveTests", - "af7c72ee-94a1-42f8-8306-395ce9147b22", + "TestVersion", + "[TestNamespace]/TestName", + "TestInstance", "PC", "Other", "Windows 10", @@ -236,7 +236,7 @@ "Other", "8df423be-a013-477c-88b9-8186722e3d49", "7b1ad079-51c6-4824-91c1-9decb485ba64", - "dotnet7.0.20:otel1.8.1:ext1.3.0-beta.2-d", + "dotnet7.0.20:otel1.8.1:ext1.4.0-alpha.20240612-d", 1, "", "", @@ -255,15 +255,15 @@ "RequestHeaders": { "Accept": "application/json", "Authorization": "Sanitized", - "Content-Length": "174", + "Content-Length": "177", "Content-Type": "application/json", - "traceparent": "00-d90e7a2bf246528d5c75c004752cfefc-25dd917267728ea9-00", + "traceparent": "00-85f3606f8206cdd747e7ac6a7b9946e5-abd455fe22437ca3-00", "User-Agent": "azsdk-net-Monitor.Query/1.1.0 (.NET 7.0.20; Microsoft Windows 10.0.22631)", "x-ms-client-request-id": "Sanitized", "x-ms-return-client-request-id": "true" }, "RequestBody": { - "query": "AppRequests | where Url == 'http://localhost:9998/' | where AppRoleName == 'DistroWebAppLiveTests' | top 1 by TimeGenerated", + "query": "AppRequests | where Url == 'http://localhost:9998/' | where AppRoleName == '[TestNamespace]/TestName' | top 1 by TimeGenerated", "timespan": "PT30M" }, "StatusCode": 200, @@ -271,12 +271,12 @@ "Access-Control-Allow-Origin": "*", "Access-Control-Expose-Headers": "Retry-After,Age,WWW-Authenticate,x-resource-identities,x-ms-status-location", "Connection": "keep-alive", - "Content-Length": "2392", + "Content-Length": "2444", "Content-Type": "application/json; charset=utf-8", - "Date": "Fri, 07 Jun 2024 21:44:58 GMT", + "Date": "Thu, 13 Jun 2024 20:46:48 GMT", "Strict-Transport-Security": "max-age=15724800; includeSubDomains", "Vary": "Accept-Encoding", - "Via": "1.1 draft-oms-84fccbf47-vv6qb", + "Via": "1.1 draft-oms-7b5b6f666d-vdgs9", "X-Content-Type-Options": "nosniff" }, "ResponseBody": { @@ -452,29 +452,29 @@ "rows": [ [ "d49041ed-21aa-42c5-a6f3-6d60bb93d63c", - "2024-06-07T21:44:23.2035048Z", - "52f6536389172c57", + "2024-06-13T20:41:12.3567089Z", + "37f332bbeadde96b", "", "GET /", "http://localhost:9998/", true, "200", - 33.8512, + 30.5704, "<250ms", - "{\"_MS.ProcessedByMetricExtractors\":\"(Name: X,Ver:'1.1')\"}", + "{\"CustomProperty1\":\"Value1\",\"_MS.ProcessedByMetricExtractors\":\"(Name: X,Ver:'1.1')\"}", null, "GET /", - "2f5dc72a16261a3a9e74e74ca0f3b4bf", + "986a2a0d5230df5f38c79666b260ea1b", null, - "2f64853161967288", - "", - "", + "f95c51aa6574bae5", "", "", "", + "TestAuthenticatedUserId", "", - "DistroWebAppLiveTests", - "af7c72ee-94a1-42f8-8306-395ce9147b22", + "TestVersion", + "[TestNamespace]/TestName", + "TestInstance", "PC", "Other", "Windows 10", @@ -485,7 +485,7 @@ "Other", "8df423be-a013-477c-88b9-8186722e3d49", "7b1ad079-51c6-4824-91c1-9decb485ba64", - "dotnet7.0.20:otel1.8.1:ext1.3.0-beta.2-d", + "dotnet7.0.20:otel1.8.1:ext1.4.0-alpha.20240612-d", 1, "", "", @@ -504,15 +504,15 @@ "RequestHeaders": { "Accept": "application/json", "Authorization": "Sanitized", - "Content-Length": "253", + "Content-Length": "256", "Content-Type": "application/json", - "traceparent": "00-cf77b2398e6b1a6ebf333d08dae5624f-5b6a30caa64b9b65-00", + "traceparent": "00-f0864b43407e78648bb376a3941c5547-a1d7de6f1b9e9b56-00", "User-Agent": "azsdk-net-Monitor.Query/1.1.0 (.NET 7.0.20; Microsoft Windows 10.0.22631)", "x-ms-client-request-id": "Sanitized", "x-ms-return-client-request-id": "true" }, "RequestBody": { - "query": "AppMetrics | where Name == 'http.client.request.duration' | where AppRoleName == 'DistroWebAppLiveTests' | where Properties.['server.address'] == 'localhost' | top 1 by TimeGenerated", + "query": "AppMetrics | where Name == 'http.client.request.duration' | where AppRoleName == '[TestNamespace]/TestName' | where Properties.['server.address'] == 'localhost' | top 1 by TimeGenerated", "timespan": "PT30M" }, "StatusCode": 200, @@ -522,10 +522,10 @@ "Connection": "keep-alive", "Content-Length": "2097", "Content-Type": "application/json; charset=utf-8", - "Date": "Fri, 07 Jun 2024 21:44:58 GMT", + "Date": "Thu, 13 Jun 2024 20:46:48 GMT", "Strict-Transport-Security": "max-age=15724800; includeSubDomains", "Vary": "Accept-Encoding", - "Via": "1.1 draft-oms-84fccbf47-9ff58", + "Via": "1.1 draft-oms-7b5b6f666d-2mhkm", "X-Content-Type-Options": "nosniff" }, "ResponseBody": { @@ -669,13 +669,13 @@ "rows": [ [ "d49041ed-21aa-42c5-a6f3-6d60bb93d63c", - "2024-06-07T21:44:23.41758Z", + "2024-06-13T20:41:12.5611815Z", "http.client.request.duration", 1, - 0.0818636, - 0.0818636, - 0.0818636, - "{\"http.request.method\":\"GET\",\"http.response.status_code\":\"200\",\"network.protocol.version\":\"1.1\",\"server.address\":\"localhost\",\"server.port\":\"9998\",\"url.scheme\":\"http\"}", + 0.0681025, + 0.0681025, + 0.0681025, + "{\"http.request.method\":\"GET\",\"server.address\":\"localhost\",\"url.scheme\":\"http\",\"http.response.status_code\":\"200\",\"network.protocol.version\":\"1.1\",\"server.port\":\"9998\"}", "", "", "", @@ -684,9 +684,9 @@ "", "", "", - "", - "DistroWebAppLiveTests", - "af7c72ee-94a1-42f8-8306-395ce9147b22", + "TestVersion", + "[TestNamespace]/TestName", + "TestInstance", "PC", "Other", "Windows 10", @@ -697,7 +697,7 @@ "Other", "8df423be-a013-477c-88b9-8186722e3d49", "7b1ad079-51c6-4824-91c1-9decb485ba64", - "dotnet7.0.20:otel1.8.1:ext1.3.0-beta.2-d", + "dotnet7.0.20:otel1.8.1:ext1.4.0-alpha.20240612-d", "Azure", "AppMetrics", "/subscriptions/65b2f83e-7bf1-4be3-bafc-3a4163265a52/resourcegroups/rg-tileemonitor/providers/microsoft.insights/components/t48da106dbe0b9268" @@ -713,15 +713,15 @@ "RequestHeaders": { "Accept": "application/json", "Authorization": "Sanitized", - "Content-Length": "180", + "Content-Length": "183", "Content-Type": "application/json", - "traceparent": "00-154cc7d705defc8173d6ade388684c0e-58f18a6b885d49f9-00", + "traceparent": "00-05cbb9ed28473cf5f9e0fd1fcc39f621-cc5792621b837c7f-00", "User-Agent": "azsdk-net-Monitor.Query/1.1.0 (.NET 7.0.20; Microsoft Windows 10.0.22631)", "x-ms-client-request-id": "Sanitized", "x-ms-return-client-request-id": "true" }, "RequestBody": { - "query": "AppMetrics | where Name == 'http.server.request.duration' | where AppRoleName == 'DistroWebAppLiveTests' | top 1 by TimeGenerated", + "query": "AppMetrics | where Name == 'http.server.request.duration' | where AppRoleName == '[TestNamespace]/TestName' | top 1 by TimeGenerated", "timespan": "PT30M" }, "StatusCode": 200, @@ -729,12 +729,12 @@ "Access-Control-Allow-Origin": "*", "Access-Control-Expose-Headers": "Retry-After,Age,WWW-Authenticate,x-resource-identities,x-ms-status-location", "Connection": "keep-alive", - "Content-Length": "2062", + "Content-Length": "2060", "Content-Type": "application/json; charset=utf-8", - "Date": "Fri, 07 Jun 2024 21:45:00 GMT", + "Date": "Thu, 13 Jun 2024 20:46:49 GMT", "Strict-Transport-Security": "max-age=15724800; includeSubDomains", "Vary": "Accept-Encoding", - "Via": "1.1 draft-oms-84fccbf47-z4b84", + "Via": "1.1 draft-oms-7b5b6f666d-xbjmt", "X-Content-Type-Options": "nosniff" }, "ResponseBody": { @@ -878,14 +878,13 @@ "rows": [ [ "d49041ed-21aa-42c5-a6f3-6d60bb93d63c", - "2024-06-07T21:44:23.4175451Z", + "2024-06-13T20:41:12.5611445Z", "http.server.request.duration", 1, - 0.0338512, - 0.0338512, - 0.0338512, - "{\"http.request.method\":\"GET\",\"http.response.status_code\":\"200\",\"http.route\":\"/\",\"network.protocol.version\":\"1.1\",\"url.scheme\":\"http\"}", - "", + 0.0305704, + 0.0305704, + 0.0305704, + "{\"http.route\":\"/\",\"http.request.method\":\"GET\",\"url.scheme\":\"http\",\"http.response.status_code\":\"200\",\"network.protocol.version\":\"1.1\"}", "", "", "", @@ -894,8 +893,9 @@ "", "", "", - "DistroWebAppLiveTests", - "af7c72ee-94a1-42f8-8306-395ce9147b22", + "TestVersion", + "[TestNamespace]/TestName", + "TestInstance", "PC", "Other", "Windows 10", @@ -906,7 +906,7 @@ "Other", "8df423be-a013-477c-88b9-8186722e3d49", "7b1ad079-51c6-4824-91c1-9decb485ba64", - "dotnet7.0.20:otel1.8.1:ext1.3.0-beta.2-d", + "dotnet7.0.20:otel1.8.1:ext1.4.0-alpha.20240612-d", "Azure", "AppMetrics", "/subscriptions/65b2f83e-7bf1-4be3-bafc-3a4163265a52/resourcegroups/rg-tileemonitor/providers/microsoft.insights/components/t48da106dbe0b9268" @@ -922,15 +922,15 @@ "RequestHeaders": { "Accept": "application/json", "Authorization": "Sanitized", - "Content-Length": "173", + "Content-Length": "176", "Content-Type": "application/json", - "traceparent": "00-9759964878634526a529cfa2071f06c9-0d03ec30c641be54-00", + "traceparent": "00-1026ff1d3d19f40039319c5ae263b712-1f07ea18488b7b43-00", "User-Agent": "azsdk-net-Monitor.Query/1.1.0 (.NET 7.0.20; Microsoft Windows 10.0.22631)", "x-ms-client-request-id": "Sanitized", "x-ms-return-client-request-id": "true" }, "RequestBody": { - "query": "AppTraces | where Message == 'Message via ILogger' | where AppRoleName == 'DistroWebAppLiveTests' | top 1 by TimeGenerated", + "query": "AppTraces | where Message == 'Message via ILogger' | where AppRoleName == '[TestNamespace]/TestName' | top 1 by TimeGenerated", "timespan": "PT30M" }, "StatusCode": 200, @@ -938,12 +938,12 @@ "Access-Control-Allow-Origin": "*", "Access-Control-Expose-Headers": "Retry-After,Age,WWW-Authenticate,x-resource-identities,x-ms-status-location", "Connection": "keep-alive", - "Content-Length": "1989", + "Content-Length": "2011", "Content-Type": "application/json; charset=utf-8", - "Date": "Fri, 07 Jun 2024 21:45:01 GMT", + "Date": "Thu, 13 Jun 2024 20:46:50 GMT", "Strict-Transport-Security": "max-age=15724800; includeSubDomains", "Vary": "Accept-Encoding", - "Via": "1.1 draft-oms-84fccbf47-q7zwk", + "Via": "1.1 draft-oms-7b5b6f666d-qjzzg", "X-Content-Type-Options": "nosniff" }, "ResponseBody": { @@ -1091,22 +1091,22 @@ "rows": [ [ "d49041ed-21aa-42c5-a6f3-6d60bb93d63c", - "2024-06-07T21:44:23.2335203Z", + "2024-06-13T20:41:12.3838321Z", "Message via ILogger", 1, null, null, "", - "2f5dc72a16261a3a9e74e74ca0f3b4bf", - "52f6536389172c57", - "", + "986a2a0d5230df5f38c79666b260ea1b", + "37f332bbeadde96b", "", "", "", "", "", - "DistroWebAppLiveTests", - "tilee-devbox", + "TestVersion", + "[TestNamespace]/TestName", + "TestInstance", "PC", "Other", "Windows 10", @@ -1117,7 +1117,7 @@ "Other", "8df423be-a013-477c-88b9-8186722e3d49", "7b1ad079-51c6-4824-91c1-9decb485ba64", - "dotnet7.0.20:otel1.8.1:ext1.3.0-beta.2-d", + "dotnet7.0.20:otel1.8.1:ext1.4.0-alpha.20240612-d", 1, "", "", @@ -1134,7 +1134,7 @@ "Variables": { "CONNECTION_STRING": "InstrumentationKey=7b1ad079-51c6-4824-91c1-9decb485ba64;IngestionEndpoint=https://westus-0.in.applicationinsights.azure.com/;LiveEndpoint=https://westus.livediagnostics.monitor.azure.com/;ApplicationId=8df423be-a013-477c-88b9-8186722e3d49", "LOGS_ENDPOINT": "https://api.loganalytics.io", - "RandomSeed": "1564761728", + "RandomSeed": "1609645150", "WORKSPACE_ID": "d49041ed-21aa-42c5-a6f3-6d60bb93d63c" } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests/TelemetryValidationHelper.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests/TelemetryValidationHelper.cs index 3628720bdb210..571e484bf2753 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests/TelemetryValidationHelper.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Integration.Tests/TelemetryValidationHelper.cs @@ -25,7 +25,7 @@ public static void ValidateExpectedTelemetry(string description, LogsTable? logs { if (property.Name == "Properties") { - var jsonString = row[property.Name].ToString(); + var jsonString = row[property.Name]?.ToString(); Assert.IsNotNull(jsonString, $"({description}) Expected a non-null value for {property.Name}"); var expectedProperties = property.GetValue(expectedTelemetry, null) as List>; Assert.IsNotNull(expectedProperties, $"({description}) Expected a non-null value for {nameof(expectedTelemetry)}.Properties"); @@ -37,11 +37,11 @@ public static void ValidateExpectedTelemetry(string description, LogsTable? logs } else { - TestContext.Out.WriteLine($"Property: '{property.Name}' ExpectedValue: '{property.GetValue(expectedTelemetry, null)}' ActualValue: '{row[property.Name]}'"); + TestContext.Out.WriteLine($"PropertyName: '{property.Name}' ExpectedValue: '{property.GetValue(expectedTelemetry, null)}' ActualValue: '{row[property.Name]}'"); Assert.AreEqual( - expected: property.GetValue(expectedTelemetry, null), - actual: row[property.Name], + expected: property.GetValue(expectedTelemetry, null)!.ToString(), + actual: row[property.Name].ToString(), message: $"({description}) Expected {property.Name} to be '{property.GetValue(expectedTelemetry, null)}' but found '{logsTable.Rows[0][property.Name]}'."); } } @@ -55,6 +55,10 @@ private static void ValidateProperties(string description, string jsonString, Li var jsonNode = JsonNode.Parse(jsonString); Assert.IsNotNull(jsonNode, $"({description}) Expected a non-null JSON node."); + var expectedCount = expectedProperties.Count; + var actualCount = ((JsonObject)jsonNode!).Count; + Assert.AreEqual(expectedCount, actualCount, $"({description}) Expected {expectedCount} properties but found {actualCount}."); + foreach (var expectedProperty in expectedProperties) { var jsonValue = jsonNode![expectedProperty.Key]; @@ -71,37 +75,66 @@ private static void ValidateProperties(string description, string jsonString, Li #endif } + /* + * Notes on field validation: + * Remember that this test will be run as both a Recording and Live. + * We can't include unique ids becasue they are unique per test run (ie: Id, OperationId, ParentId, etc). + * We can't include timing fields because would be unique per test run(ie: TimeGenerated, DurationMS, PerformanceBucket). + * We can't include client fields because we can't control where these tests run (ie: ClientOS, ClientCity, ClientCountryOrRegion, etc). + */ + public struct ExpectedAppDependency { + public string Target { get; set; } + public string DependencyType { get; set; } + public string Name { get; set; } public string Data { get; set; } + public string Success { get; set; } + public string ResultCode { get; set; } + public List> Properties { get; set; } + public string UserAuthenticatedId { get; set; } + public string AppVersion { get; set; } public string AppRoleName { get; set; } - - // TODO: ADD REMAINING PROPERTIES IN FOLLOW UP PR. + public string AppRoleInstance { get; set; } + public string ClientIP { get; set; } + public string Type { get; set; } } public struct ExpectedAppRequest { + public string Name { get; set; } public string Url { get; set; } + public string Success { get; set; } + public string ResultCode { get; set; } + public List> Properties { get; set; } + public string OperationName { get; set; } + public string UserAuthenticatedId { get; set; } + public string AppVersion { get; set; } public string AppRoleName { get; set; } - - // TODO: ADD REMAINING PROPERTIES IN FOLLOW UP PR. + public string AppRoleInstance { get; set; } + public string ClientIP { get; set; } + public string Type { get; set; } } public struct ExpectedAppMetric { public string Name { get; set; } - public string AppRoleName { get; set; } public List> Properties { get; set; } - - // TODO: ADD REMAINING PROPERTIES IN FOLLOW UP PR. + public string AppVersion { get; set; } + public string AppRoleName { get; set; } + public string AppRoleInstance { get; set; } + public string Type { get; set; } } public struct ExpectedAppTrace { public string Message { get; set; } + public string SeverityLevel { get; set; } + public string AppVersion { get; set; } public string AppRoleName { get; set; } - - // TODO: ADD REMAINING PROPERTIES IN FOLLOW UP PR. + public string AppRoleInstance { get; set; } + public string ClientIP { get; set; } + public string Type { get; set; } } } }