diff --git a/dotnet/src/Functions/Functions.OpenApi/RestApiOperationRunner.cs b/dotnet/src/Functions/Functions.OpenApi/RestApiOperationRunner.cs index 2a8a40e232cf..6f541b9dc55d 100644 --- a/dotnet/src/Functions/Functions.OpenApi/RestApiOperationRunner.cs +++ b/dotnet/src/Functions/Functions.OpenApi/RestApiOperationRunner.cs @@ -24,6 +24,21 @@ internal sealed class RestApiOperationRunner private const string DefaultResponseKey = "default"; + /// + /// HTTP request method. + /// + private const string HttpRequestMethod = "http.request.method"; + + /// + /// The HTTP request payload body. + /// + private const string HttpRequestBody = "http.request.body"; + + /// + /// Absolute URL describing a network resource according to RFC3986. + /// + private const string UrlFull = "url.full"; + /// /// List of payload builders/factories. /// @@ -186,9 +201,23 @@ private async Task SendAsync( } catch (HttpOperationException ex) { +#pragma warning disable CS0618 // Type or member is obsolete ex.RequestMethod = requestMessage.Method.Method; ex.RequestUri = requestMessage.RequestUri; ex.RequestPayload = payload; +#pragma warning restore CS0618 // Type or member is obsolete + + ex.Data.Add(HttpRequestMethod, requestMessage.Method.Method); + ex.Data.Add(UrlFull, requestMessage.RequestUri?.ToString()); + ex.Data.Add(HttpRequestBody, payload); + + throw; + } + catch (KernelException ex) + { + ex.Data.Add(HttpRequestMethod, requestMessage.Method.Method); + ex.Data.Add(UrlFull, requestMessage.RequestUri?.ToString()); + ex.Data.Add(HttpRequestBody, payload); throw; } diff --git a/dotnet/src/Functions/Functions.UnitTests/OpenApi/RestApiOperationRunnerTests.cs b/dotnet/src/Functions/Functions.UnitTests/OpenApi/RestApiOperationRunnerTests.cs index cb9e9b977749..c48f551c36f4 100644 --- a/dotnet/src/Functions/Functions.UnitTests/OpenApi/RestApiOperationRunnerTests.cs +++ b/dotnet/src/Functions/Functions.UnitTests/OpenApi/RestApiOperationRunnerTests.cs @@ -1048,7 +1048,11 @@ public async Task ItShouldThrowExceptionForUnsupportedContentTypeAsync() var sut = new RestApiOperationRunner(this._httpClient, this._authenticationHandlerMock.Object); // Act & Assert - await Assert.ThrowsAsync(() => sut.RunAsync(operation, arguments)); + var kernelException = await Assert.ThrowsAsync(() => sut.RunAsync(operation, arguments)); + Assert.Equal("The content type `fake/type` is not supported.", kernelException.Message); + Assert.Equal("POST", kernelException.Data["http.request.method"]); + Assert.Equal("https://fake-random-test-host/fake-path", kernelException.Data["url.full"]); + Assert.Equal("{\"value\":\"fake-value\"}", kernelException.Data["http.request.body"]); } [Fact] diff --git a/dotnet/src/IntegrationTests/Plugins/RepairServiceTests.cs b/dotnet/src/IntegrationTests/Plugins/RepairServiceTests.cs index eb625bd19559..f5da4448ef02 100644 --- a/dotnet/src/IntegrationTests/Plugins/RepairServiceTests.cs +++ b/dotnet/src/IntegrationTests/Plugins/RepairServiceTests.cs @@ -101,8 +101,8 @@ public async Task HttpOperationExceptionIncludeRequestInfoAsync() catch (HttpOperationException ex) { Assert.Equal("Response status code does not indicate success: 404 (Not Found).", ex.Message); - Assert.Equal("Patch", ex.RequestMethod); - Assert.Equal("https://piercerepairsapi.azurewebsites.net/repairs", ex.RequestUri!.ToString()); + Assert.Equal("Patch", ex.Data["http.request.method"]); + Assert.Equal("https://piercerepairsapi.azurewebsites.net/repairs", ex.Data["url.full"]); } } diff --git a/dotnet/src/SemanticKernel.Abstractions/Http/HttpOperationException.cs b/dotnet/src/SemanticKernel.Abstractions/Http/HttpOperationException.cs index 25a182244c7f..e0ac98141f8e 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Http/HttpOperationException.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Http/HttpOperationException.cs @@ -8,6 +8,10 @@ namespace Microsoft.SemanticKernel; /// /// Represents an exception specific to HTTP operations. /// +/// +/// Instances of this class optionally contain telemetry information in the Exception.Data property using keys that are consistent with the OpenTelemetry standard. +/// See https://opentelemetry.io/ for more information. +/// public class HttpOperationException : Exception { /// @@ -65,6 +69,7 @@ public HttpOperationException(HttpStatusCode? statusCode, string? responseConten /// /// This information is only available in limited circumstances e.g. when using Open API plugins. /// + [Obsolete("This property is obsolete and will be removed in a future version. Use the Exception.Data['Name'] instead.")] public string? RequestMethod { get; set; } /// @@ -73,6 +78,7 @@ public HttpOperationException(HttpStatusCode? statusCode, string? responseConten /// /// This information is only available in limited circumstances e.g. when using Open API plugins. /// + [Obsolete("This property is obsolete and will be removed in a future version. Use the Exception.Data['Url'] instead.")] public Uri? RequestUri { get; set; } /// @@ -81,5 +87,6 @@ public HttpOperationException(HttpStatusCode? statusCode, string? responseConten /// /// This information is only available in limited circumstances e.g. when using Open API plugins. /// + [Obsolete("This property is obsolete and will be removed in a future version. Use the Exception.Data['Data'] instead.")] public object? RequestPayload { get; set; } } diff --git a/dotnet/src/SemanticKernel.Abstractions/KernelException.cs b/dotnet/src/SemanticKernel.Abstractions/KernelException.cs index ea62aa07ae81..482414a94160 100644 --- a/dotnet/src/SemanticKernel.Abstractions/KernelException.cs +++ b/dotnet/src/SemanticKernel.Abstractions/KernelException.cs @@ -7,6 +7,10 @@ namespace Microsoft.SemanticKernel; /// /// Represents the base exception from which all Semantic Kernel exceptions derive. /// +/// +/// Instances of this class optionally contain telemetry information in the Exception.Data property using keys that are consistent with the OpenTelemetry standard. +/// See https://opentelemetry.io/ for more information. +/// public class KernelException : Exception { ///