Skip to content

Commit

Permalink
formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
TimHess committed Sep 16, 2024
1 parent c31ec4a commit 4b8ebf1
Show file tree
Hide file tree
Showing 12 changed files with 80 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public override void Configure(HttpExchangesEndpointOptions options)
{
options.RequestHeaders.Clear();
}

if (options.ResponseHeaders.Count == 0)
{
foreach (string defaultKey in DefaultAllowedResponseHeaders)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ public static class EndpointServiceCollectionExtensions
/// </returns>
public static IServiceCollection AddHttpExchangesActuator(this IServiceCollection services)
{
return services.AddHttpExchangesActuator(_ => { });
return services.AddHttpExchangesActuator(_ =>
{
});
}

/// <summary>
Expand All @@ -30,7 +32,9 @@ public static IServiceCollection AddHttpExchangesActuator(this IServiceCollectio
/// <param name="services">
/// The <see cref="IServiceCollection" /> to add services to.
/// </param>
/// <param name="configureOptions">A delegate to configure the <see cref="HttpExchangesEndpointOptions"/>.</param>
/// <param name="configureOptions">
/// A delegate to configure the <see cref="HttpExchangesEndpointOptions" />.
/// </param>
/// <returns>
/// The incoming <paramref name="services" /> so that additional calls can be chained.
/// </returns>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ internal sealed class HttpExchangesDiagnosticObserver : DiagnosticObserver, IHtt
private const string StopEventName = "Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop";

internal const string Redacted = "******";
internal ConcurrentQueue<HttpExchange> Queue { get; } = new();

private static readonly DateTime BaseTime = DateTime.UnixEpoch;
private readonly IOptionsMonitor<HttpExchangesEndpointOptions> _optionsMonitor;
private readonly ILogger<HttpExchangesDiagnosticObserver> _logger;
internal ConcurrentQueue<HttpExchange> Queue { get; } = new();

public HttpExchangesDiagnosticObserver(IOptionsMonitor<HttpExchangesEndpointOptions> optionsMonitor, ILoggerFactory loggerFactory)
: base(DefaultObserverName, DiagnosticName, loggerFactory)
Expand Down Expand Up @@ -71,42 +71,53 @@ private HttpExchange GetHttpExchange(HttpContext context, TimeSpan duration)
{
HttpExchangesEndpointOptions options = _optionsMonitor.CurrentValue;
string requestUri = GetRequestUri(context.Request, options.AddPathInfo);

if (options.AddQueryString)
{
requestUri += context.Request.QueryString.Value;
}

string? remoteAddress = null;

if (options.AddRemoteAddress)
{
remoteAddress = GetRemoteAddress(context);
}

Dictionary<string, IList<string?>> requestHeaders = [];

if (options.AddRequestHeaders)
{
requestHeaders = GetHeaders(context.Request.Headers, options.RequestHeaders);
}

var traceRequest = new TraceRequest(context.Request.Method, requestUri, requestHeaders, remoteAddress);

Dictionary<string, IList<string?>> responseHeaders = [];

if (options.AddResponseHeaders)
{
responseHeaders = GetHeaders(context.Response.Headers, options.ResponseHeaders);
}

var traceResponse = new TraceResponse(context.Response.StatusCode, responseHeaders);

string? userName = null;

if (options.AddUserPrincipal)
{
userName = GetUserPrincipal(context);
}

TracePrincipal? principal = userName == null ? null : new TracePrincipal(userName);

string? sessionId = null;

if (options.AddSessionId)
{
sessionId = GetSessionId(context);
}

TraceSession? session = sessionId == null ? null : new TraceSession(sessionId);

long timestamp = GetJavaTime(DateTime.UtcNow.Ticks);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ internal sealed class HttpExchangesEndpointHandler : IHttpExchangesEndpointHandl

public EndpointOptions Options => _optionsMonitor.CurrentValue;

public HttpExchangesEndpointHandler(IOptionsMonitor<HttpExchangesEndpointOptions> optionsMonitor, IHttpExchangesRepository httpExchangeRepository, ILoggerFactory loggerFactory)
public HttpExchangesEndpointHandler(IOptionsMonitor<HttpExchangesEndpointOptions> optionsMonitor, IHttpExchangesRepository httpExchangeRepository,
ILoggerFactory loggerFactory)
{
ArgumentNullException.ThrowIfNull(optionsMonitor);
ArgumentNullException.ThrowIfNull(httpExchangeRepository);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,17 @@ public sealed class HttpExchangesEndpointOptions : EndpointOptions
/// <summary>
/// Gets or sets a value indicating whether HTTP headers from the request should be included in traces.
/// <para>
/// If a request header is not present in the <see cref="RequestHeaders"/>,
/// the header name will be logged with a redacted value.
/// Request headers can contain authentication tokens,
/// or private information which may have regulatory concerns
/// under GDPR and other laws. Arbitrary request headers
/// should not be logged unless logs are secure and
/// access controlled and the privacy impact assessed.
/// If a request header is not present in the <see cref="RequestHeaders" />, the header name will be logged with a redacted value. Request headers can
/// contain authentication tokens, or private information which may have regulatory concerns under GDPR and other laws. Arbitrary request headers should
/// not be logged unless logs are secure and access controlled and the privacy impact assessed.
/// </para>
/// </summary>
public bool AddRequestHeaders { get; set; } = true;

/// <summary>
/// Gets or sets a value indicating whether HTTP headers from the response should be included in traces.
/// <para>
/// If a response header is not present in the <see cref="ResponseHeaders"/>,
/// the header name will be logged with a redacted value.
/// If a response header is not present in the <see cref="ResponseHeaders" />, the header name will be logged with a redacted value.
/// </para>
/// </summary>
public bool AddResponseHeaders { get; set; } = true;
Expand Down Expand Up @@ -69,18 +64,17 @@ public sealed class HttpExchangesEndpointOptions : EndpointOptions
/// <summary>
/// Gets request header values that are allowed to be logged.
/// <para>
/// If a request header is not present in the <see cref="RequestHeaders"/>, the header name will be logged with a redacted value.
/// Request headers can contain authentication tokens, or private information which may have regulatory concerns
/// under GDPR and other laws. Arbitrary request headers should not be logged unless logs are secure and
/// access controlled and the privacy impact assessed.
/// If a request header is not present in the <see cref="RequestHeaders" />, the header name will be logged with a redacted value. Request headers can
/// contain authentication tokens, or private information which may have regulatory concerns under GDPR and other laws. Arbitrary request headers should
/// not be logged unless logs are secure and access controlled and the privacy impact assessed.
/// </para>
/// </summary>
public IList<string> RequestHeaders { get; private set; } = new List<string>();

/// <summary>
/// Gets response header values that are allowed to be logged.
/// <para>
/// If a response header is not present in the <see cref="ResponseHeaders"/>, the header name will be logged with a redacted value.
/// If a response header is not present in the <see cref="ResponseHeaders" />, the header name will be logged with a redacted value.
/// </para>
/// </summary>
public IList<string> ResponseHeaders { get; private set; } = new List<string>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.


// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

namespace Steeltoe.Management.Endpoint.Actuators.HttpExchanges;

public interface IHttpExchangesEndpointHandler : IEndpointHandler<object?, HttpExchangesResult>;
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ internal static class ServiceCollectionExtensions
/// <param name="services">
/// The <see cref="IServiceCollection" /> to add services to.
/// </param>
/// <param name="configureOptions">A delegate to configure the <see cref="HttpExchangesEndpointOptions"/>.</param>
/// <param name="configureOptions">
/// A delegate to configure the <see cref="HttpExchangesEndpointOptions" />.
/// </param>
/// <returns>
/// The incoming <paramref name="services" /> so that additional calls can be chained.
/// </returns>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ public Links Invoke(string baseUrl)
{
if (!links.Entries.ContainsKey(endpointOptions.Id))
{
if (baseUrl.EndsWith(ConfigureManagementOptions.DefaultCloudFoundryPath, StringComparison.OrdinalIgnoreCase) && endpointOptions.Id == "httpexchanges")
if (baseUrl.EndsWith(ConfigureManagementOptions.DefaultCloudFoundryPath, StringComparison.OrdinalIgnoreCase) &&
endpointOptions.Id == "httpexchanges")
{
string linkPath = $"{baseUrl.TrimEnd('/')}/httptrace";
links.Entries.Add("httptrace", new Link(linkPath));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,13 @@ public async Task CloudFoundryEndpointMiddleware_ReturnsExpectedData()
HttpClient client = server.CreateClient();
var links = await client.GetFromJsonAsync<Links>("http://localhost/cloudfoundryapplication", SerializerOptions);
links.Should().NotBeNull();
links!.Entries.Should().ContainKeys(["self", "info", "httptrace"]);

links!.Entries.Should().ContainKeys([
"self",
"info",
"httptrace"
]);

links.Entries["self"].Href.Should().Be("http://localhost/cloudfoundryapplication");
links.Entries["info"].Href.Should().Be("http://localhost/cloudfoundryapplication/info");
links.Entries["httptrace"].Href.Should().Be("http://localhost/cloudfoundryapplication/httptrace");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ public void RoutesByPathAndVerb()

endpointOptions.RequiresExactMatch().Should().BeTrue();
endpointOptions.GetPathMatchPattern(managementOptions, managementOptions.Path).Should().Be("/actuator/httpexchanges");
endpointOptions.GetPathMatchPattern(managementOptions, ConfigureManagementOptions.DefaultCloudFoundryPath).Should().Be("/cloudfoundryapplication/httptrace");

endpointOptions.GetPathMatchPattern(managementOptions, ConfigureManagementOptions.DefaultCloudFoundryPath).Should()
.Be("/cloudfoundryapplication/httptrace");

endpointOptions.AllowedVerbs.Should().Contain(verb => verb == "Get");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,35 @@ public async Task AddHttpExchangesActuator_AllowsChangingRedactionOptions()
{
["management:endpoints:httpExchanges:enabled"] = "true"
};

var services = new ServiceCollection();
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddInMemoryCollection(appSettings);
IConfiguration configuration = configurationBuilder.Build();
services.AddLogging();
services.AddSingleton(configuration);

services.AddHttpExchangesActuator(traceOptions =>
{
traceOptions.RequestHeaders.Add("Header1");
});

await using ServiceProvider serviceProvider = services.BuildServiceProvider(true);
var options = serviceProvider.GetRequiredService<IOptionsMonitor<HttpExchangesEndpointOptions>>();

HttpContext context = HttpExchangesDiagnosticObserverTest.CreateRequest();
Dictionary<string, IList<string?>> result = HttpExchangesDiagnosticObserver.GetHeaders(context.Request.Headers, options.CurrentValue.RequestHeaders);

result.Should().NotBeNull();
result.Should().ContainKeys(["accept", "authorization", "host", "user-agent", "header1", "header2"]);

result.Should().ContainKeys([
"accept",
"authorization",
"host",
"user-agent",
"header1",
"header2"
]);
#pragma warning disable S4040 // Strings should be normalized to uppercase
result[HeaderNames.Accept.ToLower(CultureInfo.InvariantCulture)][0].Should().Be("testContent");
result[HeaderNames.Authorization.ToLower(CultureInfo.InvariantCulture)][0].Should().Be(HttpExchangesDiagnosticObserver.Redacted);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,24 @@ public void GetRequestUri_ReturnsExpected()
[Fact]
public void GetHeaders_ReturnsExpected()
{
IOptionsMonitor<HttpExchangesEndpointOptions> option = GetOptionsMonitorFromSettings<HttpExchangesEndpointOptions, ConfigureHttpExchangesEndpointOptions>();
IOptionsMonitor<HttpExchangesEndpointOptions> option =
GetOptionsMonitorFromSettings<HttpExchangesEndpointOptions, ConfigureHttpExchangesEndpointOptions>();

HttpContext context = CreateRequest();
context.Response.StatusCode = StatusCodes.Status100Continue;

Dictionary<string, IList<string?>> result = HttpExchangesDiagnosticObserver.GetHeaders(context.Request.Headers, option.CurrentValue.RequestHeaders);

result.Should().NotBeNull();
result.Should().ContainKeys(["accept", "authorization", "host", "user-agent", "header1", "header2"]);

result.Should().ContainKeys([
"accept",
"authorization",
"host",
"user-agent",
"header1",
"header2"
]);
#pragma warning disable S4040 // Strings should be normalized to uppercase
result[HeaderNames.Accept.ToLower(CultureInfo.InvariantCulture)][0].Should().Be("testContent");
result[HeaderNames.Authorization.ToLower(CultureInfo.InvariantCulture)][0].Should().Be(HttpExchangesDiagnosticObserver.Redacted);
Expand Down Expand Up @@ -125,7 +135,8 @@ public void GetProperty_WithProperties_ReturnsExpected()
[Fact]
public void ProcessEvent_IgnoresUnprocessableEvents()
{
IOptionsMonitor<HttpExchangesEndpointOptions> option = GetOptionsMonitorFromSettings<HttpExchangesEndpointOptions, ConfigureHttpExchangesEndpointOptions>();
IOptionsMonitor<HttpExchangesEndpointOptions> option =
GetOptionsMonitorFromSettings<HttpExchangesEndpointOptions, ConfigureHttpExchangesEndpointOptions>();

var observer = new HttpExchangesDiagnosticObserver(option, NullLoggerFactory.Instance);

Expand All @@ -152,7 +163,9 @@ public void ProcessEvent_IgnoresUnprocessableEvents()
public async Task Subscribe_Listener_StopActivity_AddsToQueue()
{
using var listener = new DiagnosticListener("Microsoft.AspNetCore");
IOptionsMonitor<HttpExchangesEndpointOptions> option = GetOptionsMonitorFromSettings<HttpExchangesEndpointOptions, ConfigureHttpExchangesEndpointOptions>();

IOptionsMonitor<HttpExchangesEndpointOptions> option =
GetOptionsMonitorFromSettings<HttpExchangesEndpointOptions, ConfigureHttpExchangesEndpointOptions>();

using var observer = new HttpExchangesDiagnosticObserver(option, NullLoggerFactory.Instance);
observer.Subscribe(listener);
Expand Down Expand Up @@ -180,7 +193,8 @@ public async Task Subscribe_Listener_StopActivity_AddsToQueue()
[Fact]
public void ProcessEvent_AddsToQueue()
{
IOptionsMonitor<HttpExchangesEndpointOptions> option = GetOptionsMonitorFromSettings<HttpExchangesEndpointOptions, ConfigureHttpExchangesEndpointOptions>();
IOptionsMonitor<HttpExchangesEndpointOptions> option =
GetOptionsMonitorFromSettings<HttpExchangesEndpointOptions, ConfigureHttpExchangesEndpointOptions>();

var observer = new HttpExchangesDiagnosticObserver(option, NullLoggerFactory.Instance);

Expand All @@ -203,7 +217,8 @@ public void ProcessEvent_AddsToQueue()
[Fact]
public void ProcessEvent_HonorsCapacity()
{
IOptionsMonitor<HttpExchangesEndpointOptions> option = GetOptionsMonitorFromSettings<HttpExchangesEndpointOptions, ConfigureHttpExchangesEndpointOptions>();
IOptionsMonitor<HttpExchangesEndpointOptions> option =
GetOptionsMonitorFromSettings<HttpExchangesEndpointOptions, ConfigureHttpExchangesEndpointOptions>();

var observer = new HttpExchangesDiagnosticObserver(option, NullLoggerFactory.Instance);
var current = new Activity("Microsoft.AspNetCore.Hosting.HttpRequestIn");
Expand All @@ -226,7 +241,9 @@ public void ProcessEvent_HonorsCapacity()
public void GetTraces_ReturnsTraces()
{
using var listener = new DiagnosticListener("test");
IOptionsMonitor<HttpExchangesEndpointOptions> option = GetOptionsMonitorFromSettings<HttpExchangesEndpointOptions, ConfigureHttpExchangesEndpointOptions>();

IOptionsMonitor<HttpExchangesEndpointOptions> option =
GetOptionsMonitorFromSettings<HttpExchangesEndpointOptions, ConfigureHttpExchangesEndpointOptions>();

var observer = new HttpExchangesDiagnosticObserver(option, NullLoggerFactory.Instance);
var current = new Activity("Microsoft.AspNetCore.Hosting.HttpRequestIn");
Expand Down

0 comments on commit 4b8ebf1

Please sign in to comment.