Skip to content

Commit

Permalink
Fixes onboard APIs with OpenAPI specs. Closes dotnet#786 (dotnet#788)
Browse files Browse the repository at this point in the history
Adds tracing to ApiCenterClient. Closes dotnet#785
Fixes generating OpenAPI specs. Closes dotnet#787
  • Loading branch information
waldekmastykarz authored Jun 14, 2024
1 parent 16c89a6 commit 8f689bb
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 11 deletions.
6 changes: 6 additions & 0 deletions dev-proxy-abstractions/ProxyUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ public static class ProxyUtils

public static readonly string ReportsKey = "Reports";

static ProxyUtils()
{
// convert enum values to camelCase
jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
}

public static bool IsGraphRequest(Request request) => IsGraphUrl(request.RequestUri);

public static bool IsGraphUrl(Uri uri) =>
Expand Down
6 changes: 4 additions & 2 deletions dev-proxy-plugins/ApiCenter/ApiCenterClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ internal ApiCenterClient(ApiCenterClientConfiguration configuration, ILogger log

_authenticationHandler = new AuthenticationDelegatingHandler(_credential, _scopes)
{
InnerHandler = new HttpClientHandler()
InnerHandler = new TracingDelegatingHandler(logger) {
InnerHandler = new HttpClientHandler()
}
};
_httpClient = new HttpClient(_authenticationHandler);

Expand Down Expand Up @@ -232,6 +234,6 @@ internal async Task<HttpResponseMessage> PostImportSpecification(ApiSpecImport a
internal async Task<ApiSpecExportResult?> PostExportSpecification(string definitionId)
{
var definitionRes = await _httpClient.PostAsync($"https://management.azure.com{definitionId}/exportSpecification?api-version=2024-03-01", null);
return await definitionRes.Content.ReadFromJsonAsync<ApiSpecExportResult>();
return await definitionRes.Content.ReadFromJsonAsync<ApiSpecExportResult>(ProxyUtils.JsonSerializerOptions);
}
}
6 changes: 0 additions & 6 deletions dev-proxy-plugins/ApiCenter/Models.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Text.Json.Serialization;
using Microsoft.OpenApi.Models;

namespace Microsoft.DevProxy.Plugins.RequestLogs.ApiCenter;
Expand All @@ -25,9 +24,7 @@ internal class ApiProperties
public ApiContact[] Contacts { get; set; } = [];
public dynamic CustomProperties { get; set; } = new object();
public string? Description { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public ApiKind? Kind { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public ApiLifecycleStage? LifecycleStage { get; set; }
public string? Title { get; set; }
public string? Summary { get; set; }
Expand Down Expand Up @@ -79,7 +76,6 @@ internal class ApiDefinitionPropertiesSpecification

internal class ApiSpecImport
{
[JsonConverter(typeof(JsonStringEnumConverter))]
public ApiSpecImportResultFormat Format { get; set; }
public ApiSpecImportRequestSpecification? Specification { get; set; }
public string? Value { get; set; }
Expand All @@ -93,7 +89,6 @@ internal class ApiSpecImportRequestSpecification

internal class ApiSpecExportResult
{
[JsonConverter(typeof(JsonStringEnumConverter))]
public ApiSpecExportResultFormat? Format { get; set; }
public string? Value { get; set; }
}
Expand All @@ -108,7 +103,6 @@ internal class ApiVersion

internal class ApiVersionProperties
{
[JsonConverter(typeof(JsonStringEnumConverter))]
public ApiLifecycleStage LifecycleStage { get; set; }
public string? Title { get; set; }
}
Expand Down
2 changes: 1 addition & 1 deletion dev-proxy-plugins/Reporters/PlainTextReporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public PlainTextReporter(IPluginEvents pluginEvents, IProxyContext context, ILog

if (_transformers.TryGetValue(reportType, out var transform))
{
Logger.LogDebug("Transforming {reportType} using {transform}...", reportType.Name, transform.Method);
Logger.LogDebug("Transforming {reportType} using {transform}...", reportType.Name, transform.Method.Name);

return transform(report.Value);
}
Expand Down
11 changes: 10 additions & 1 deletion dev-proxy-plugins/RequestLogs/ApiCenterOnboardingPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,16 @@ async Task ImportApiDefinition(string apiDefinitionId, string openApiSpecFilePat
}
else
{
Logger.LogError("Failed to import API definition for {apiDefinition}. Status: {status}, reason: {reason}", apiDefinitionId, res.StatusCode, res.ReasonPhrase);
var resContent = res.ReasonPhrase;
try
{
resContent = await res.Content.ReadAsStringAsync();
}
catch
{
}

Logger.LogError("Failed to import API definition for {apiDefinition}. Status: {status}, reason: {reason}", apiDefinitionId, res.StatusCode, resContent);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ private string ParametrizePath(OpenApiPathItem pathItem, Uri requestUri)
if (IsParametrizable(segment))
{
var parameterName = $"{previousSegment}-id";
segments[i] = "{" + parameterName + "}/";
segments[i] = $"{{{parameterName}}}{(requestUri.Segments[i].EndsWith('/') ? "/" : "")}";

pathItem.Parameters.Add(new OpenApiParameter
{
Expand Down
42 changes: 42 additions & 0 deletions dev-proxy-plugins/TracingDelegatingHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.Extensions.Logging;

namespace Microsoft.DevProxy.Plugins;

internal class TracingDelegatingHandler(ILogger logger) : DelegatingHandler
{
private readonly ILogger _logger = logger;

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
using var scope = _logger.BeginScope(request.GetHashCode().ToString());

_logger.LogTrace("Request: {method} {uri}", request.Method, request.RequestUri);
foreach (var (header, value) in request.Headers)
{
_logger.LogTrace("{header}: {value}", header, string.Join(", ", value));
}
if (request.Content is not null)
{
var body = await request.Content.ReadAsStringAsync();
_logger.LogTrace("Body: {body}", body);
}

var response = await base.SendAsync(request, cancellationToken);

_logger.LogTrace("Response");
foreach (var (header, value) in response.Headers)
{
_logger.LogTrace("{header}: {value}", header, string.Join(", ", value));
}
if (response.Content is not null)
{
var body = await response.Content.ReadAsStringAsync();
_logger.LogTrace("Body: {body}", body);
}

return response;
}
}
10 changes: 10 additions & 0 deletions dev-proxy/Logging/ProxyConsoleFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,21 @@ private void WriteMessageBoxedWithInvertedLabels(string? message, LogLevel logLe
{
scopeProvider.ForEachScope((scope, state) =>
{
if (scope is null)
{
return;
}

if (scope is string scopeString)
{
textWriter.Write(scopeString);
textWriter.Write(": ");
}
else if (scope.GetType().Name == "FormattedLogValues")
{
textWriter.Write(scope.ToString());
textWriter.Write(": ");
}
}, textWriter);
}

Expand Down

0 comments on commit 8f689bb

Please sign in to comment.