From 9618cd84e2143ae313b2a1d606af2407d47a40c6 Mon Sep 17 00:00:00 2001 From: Timothy Mothra Date: Wed, 12 Oct 2022 13:51:57 -0700 Subject: [PATCH] [AzureMonitorExporter] add `Activity` Exception demo and test (#31764) * activity exception demo and test * cleanup usings --- .../Traces/TraceDemo.cs | 13 ++++ .../TelemetryItemValidationHelper.cs | 43 +++++++++++++- .../E2ETelemetryItemValidation/TracesTests.cs | 59 +++++++++++++++++++ 3 files changed, 113 insertions(+), 2 deletions(-) diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Demo/Traces/TraceDemo.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Demo/Traces/TraceDemo.cs index ee354255b3554..1c4618ffede3b 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Demo/Traces/TraceDemo.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Demo/Traces/TraceDemo.cs @@ -65,6 +65,19 @@ public void GenerateTraces() nestedActivity?.SetStatus(ActivityStatusCode.Ok); } } + + using (var activity = activitySource.StartActivity("ExceptionExample")) + { + try + { + throw new Exception("Test exception"); + } + catch (Exception ex) + { + activity?.SetStatus(ActivityStatusCode.Error); + activity?.RecordException(ex); + } + } } public void Dispose() diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/CommonTestFramework/TelemetryItemValidationHelper.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/CommonTestFramework/TelemetryItemValidationHelper.cs index a88cc2bd42216..ff15d3b604c03 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/CommonTestFramework/TelemetryItemValidationHelper.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/CommonTestFramework/TelemetryItemValidationHelper.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -132,6 +133,7 @@ public static void AssertLog_As_ExceptionTelemetry( Assert.Equal(expectedMessage, telemetryExceptionDetails.Message); Assert.Equal(expectedTypeName, telemetryExceptionDetails.TypeName); Assert.True(telemetryExceptionDetails.ParsedStack.Any()); + Assert.Null(telemetryExceptionDetails.Stack); } public static void AssertActivity_As_DependencyTelemetry( @@ -139,7 +141,8 @@ public static void AssertActivity_As_DependencyTelemetry( string expectedName, string expectedTraceId, string expectedSpanId, - IDictionary expectedProperties) + IDictionary expectedProperties, + bool expectedSuccess = true) { Assert.Equal("RemoteDependency", telemetryItem.Name); // telemetry type Assert.Equal("RemoteDependencyData", telemetryItem.Data.BaseType); // telemetry data type @@ -155,6 +158,7 @@ public static void AssertActivity_As_DependencyTelemetry( var remoteDependencyData = (RemoteDependencyData)telemetryItem.Data.BaseData; Assert.Equal(expectedSpanId, remoteDependencyData.Id); Assert.Equal(expectedName, remoteDependencyData.Name); + Assert.Equal(expectedSuccess, remoteDependencyData.Success); if (expectedProperties == null) { @@ -175,7 +179,8 @@ public static void AssertActivity_As_RequestTelemetry( string expectedName, string expectedTraceId, IDictionary expectedProperties, - string expectedSpanId) + string expectedSpanId, + bool expectedSuccess = true) { Assert.Equal("Request", telemetryItem.Name); // telemetry type Assert.Equal("RequestData", telemetryItem.Data.BaseType); // telemetry data type @@ -201,6 +206,7 @@ public static void AssertActivity_As_RequestTelemetry( var requestData = (RequestData)telemetryItem.Data.BaseData; Assert.Equal(expectedName, requestData.Name); Assert.Equal(expectedSpanId, requestData.Id); + Assert.Equal(expectedSuccess, requestData.Success); if (expectedProperties == null) { @@ -214,5 +220,38 @@ public static void AssertActivity_As_RequestTelemetry( } } } + + internal static void AssertActivity_RecordedException( + TelemetryItem telemetryItem, + string expectedExceptionMessage, + string expectedExceptionTypeName, + string expectedTraceId, + string expectedSpanId) + { + Assert.Equal("Exception", telemetryItem.Name); // telemetry type + Assert.Equal("ExceptionData", telemetryItem.Data.BaseType); // telemetry data type + Assert.Equal(2, telemetryItem.Data.BaseData.Version); // telemetry api version + Assert.Equal("00000000-0000-0000-0000-000000000000", telemetryItem.InstrumentationKey); + + Assert.Equal(5, telemetryItem.Tags.Count); + Assert.Equal(expectedSpanId, telemetryItem.Tags["ai.operation.parentId"]); + Assert.Equal(expectedTraceId, telemetryItem.Tags["ai.operation.id"]); + Assert.Contains("ai.cloud.role", telemetryItem.Tags.Keys); + Assert.Contains("ai.cloud.roleInstance", telemetryItem.Tags.Keys); + Assert.Contains("ai.internal.sdkVersion", telemetryItem.Tags.Keys); + + var telemetryExceptionData = (TelemetryExceptionData)telemetryItem.Data.BaseData; + Assert.Null(telemetryExceptionData.SeverityLevel); + Assert.Empty(telemetryExceptionData.Properties); + + Assert.Equal(1, telemetryExceptionData.Exceptions.Count); + + var telemetryExceptionDetails = (TelemetryExceptionDetails)telemetryExceptionData.Exceptions[0]; + Assert.Equal(expectedExceptionMessage, telemetryExceptionDetails.Message); + Assert.Equal(expectedExceptionTypeName, telemetryExceptionDetails.TypeName); + Assert.True(telemetryExceptionDetails.HasFullStack); + Assert.Empty(telemetryExceptionDetails.ParsedStack); + Assert.False(string.IsNullOrEmpty(telemetryExceptionDetails.Stack)); + } } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/E2ETelemetryItemValidation/TracesTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/E2ETelemetryItemValidation/TracesTests.cs index bc6444fec83ac..427e1354e5c54 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/E2ETelemetryItemValidation/TracesTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/E2ETelemetryItemValidation/TracesTests.cs @@ -122,6 +122,65 @@ public void VerifyTrace_CreatesRequest(ActivityKind activityKind) expectedProperties: new Dictionary { { "integer", "1" }, { "message", "Hello World!" }, { "intArray", "1,2,3" } }); } + [Fact] + public void VerifyExceptionWithinActivity() + { + // SETUP + var uniqueTestId = Guid.NewGuid(); + + var activitySourceName = $"activitySourceName{uniqueTestId}"; + using var activitySource = new ActivitySource(activitySourceName); + + var tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddSource(activitySourceName) + .AddAzureMonitorTraceExporterForTest(out ConcurrentBag telemetryItems) + .Build(); + + // ACT + string spanId = null, traceId = null; + + using (var activity = activitySource.StartActivity(name: "ActivityWithException")) + { + traceId = activity.TraceId.ToHexString(); + spanId = activity.SpanId.ToHexString(); + + try + { + throw new Exception("Test exception"); + } + catch (Exception ex) + { + activity?.SetStatus(ActivityStatusCode.Error); + activity?.RecordException(ex); + } + } + + // CLEANUP + tracerProvider.Dispose(); + + // ASSERT + Assert.True(telemetryItems.Any(), "Unit test failed to collect telemetry."); + this.telemetryOutput.Write(telemetryItems); + var activityTelemetryItem = telemetryItems.First(x => x.Name == "RemoteDependency"); // TODO: Change to Single(). Still investigating random duplicate export which only repros on build server. + + TelemetryItemValidationHelper.AssertActivity_As_DependencyTelemetry( + telemetryItem: activityTelemetryItem, + expectedName: "ActivityWithException", + expectedTraceId: traceId, + expectedSpanId: spanId, + expectedProperties: null, + expectedSuccess: false); + + var exceptionTelemetryItem = telemetryItems.First(x => x.Name == "Exception"); + + TelemetryItemValidationHelper.AssertActivity_RecordedException( + telemetryItem: exceptionTelemetryItem, + expectedExceptionMessage: "Test exception", + expectedExceptionTypeName: "System.Exception", + expectedTraceId: traceId, + expectedSpanId: spanId); + } + [Theory] [InlineData(LogLevel.Information, "Information")] [InlineData(LogLevel.Warning, "Warning")]