Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trace Actuator Updates #1356

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open

Trace Actuator Updates #1356

wants to merge 10 commits into from

Conversation

TimHess
Copy link
Member

@TimHess TimHess commented Sep 12, 2024

Description

While adding support for header filtering, I discovered we're a bit out of sync with Spring on this actuator in general, so I also decided to:

  • Remove everything related to the /trace (v1) actuator
  • Rename httptrace to httpexchanges (to align with Spring Boot 3), but still use /httptrace on TPCF

I also ran into an unhandled exception with the mappings actuator and included a fix for that

Fixes #1351

Quality checklist

  • Your code complies with our Coding Style.
  • You've updated unit and/or integration tests for your change, where applicable.
  • You've updated documentation for your change, where applicable.
    If your change affects other repositories, such as Documentation, Samples and/or MainSite, add linked PRs here.
  • There's an open issue for the PR that you are making. If you'd like to propose a new feature or change, please open an issue to discuss the change or find an existing issue.
  • You've added required license files and/or file headers (explaining where the code came from with proper attribution), where code is copied from StackOverflow, a blog, or OSS.

@TimHess TimHess added Type/enhancement New feature or request Component/Management Issues related to Steeltoe Management (actuators) ReleaseLine/4.x Identified as a feature/fix for the 4.x release line labels Sep 12, 2024
@TimHess TimHess added this to the 4.0.0-m1 milestone Sep 12, 2024
@TimHess TimHess self-assigned this Sep 12, 2024
@TimHess
Copy link
Member Author

TimHess commented Sep 13, 2024

/azp run cleanup-code

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link

Code cleanup failed to reformat and push changes.

View details here.

@TimHess TimHess force-pushed the feature/header-masking-httptrace branch from a51e47c to ba2fd37 Compare September 13, 2024 13:57
Copy link
Member

@bart-vmware bart-vmware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First glance over the changes. It's pretty hard to see what's changed on GitHub due to renames. I'll run a file-by-file diff in the next round.

@TimHess TimHess force-pushed the feature/header-masking-httptrace branch 3 times, most recently from 3609088 to 27ff773 Compare September 16, 2024 20:15
return new HttpExchangesResult(recentExchanges);
}

public override void ProcessEvent(string eventName, object? value)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just type-check value for HttpContext instead of the reflection-based properly lookups? Same for the other two observers. I suspect this is a leftover from the split between base/core and support for the full .NET framework.

Copy link
Member

@bart-vmware bart-vmware Sep 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should drop HttpClientDesktopObserver (which reads from WebRequest) entirely, because it was obsoleted in .NET 6.

Then rename the renaming remaining one to just HttpClientObserver.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value is an anonymous type with a HttpContext as a property... I can't seem to get type checking to work in this scenario and haven't found anything in several web searches.

You're probably right about the other observers, I'm not sure yet if it's something I want to include in this PR though

Copy link
Member

@bart-vmware bart-vmware Sep 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value is an anonymous type with a HttpContext as a property... I can't seem to get type checking to work in this scenario and haven't found anything in several web searches.

These are just the tests acting silly by passing an anonymous type. As can be seen here, ASP.NET always passes in a HttpContext instance. So the code can just be:

if (value is HttpContext httpContext)
{
    RecordHttpExchange(current, httpContext);
}

When using that and debugging the test AddSeveralActuators_WebApplicationBuilder_NoConflict (breakpoint in method HttpExchangesDiagnosticObserver.ProcessEvent), it always sends an HttpContext instance.

You're probably right about the other observers, I'm not sure yet if it's something I want to include in this PR though

Why not, do you have any concerns? The current names are just confusing. The other observers suffer from the same issues as addressed in this PR, such as obtaining Activity.Current when not needed. Please open an issue otherwise.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are just the tests acting silly by passing an anonymous type...

I should know by now not to trust that these tests are always doing the right thing 🙄

Why not, do you have any concerns? The current names are just confusing. The other observers suffer from the same issues as addressed in this PR, such as obtaining Activity.Current when not needed. Please open an issue otherwise.

Not any specific concerns, more a question of maintaining focus... If I have time to tackle it before your next review I will, otherwise I'll create a follow-up issue so this PR doesn't keep dragging on

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can do the delete & rename, but it looks like we can't currently adjust the reflection-based property access code: dotnet/runtime#82910

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what's going on. The code was updated to type-check for HttpContext as I suggested, and now only unit tests call the method GetHttpContextPropertyValue (so it has basically become redundant). But the runtime issue states we still need to use reflection?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right... I didn't come back and check on other usages of GetHttpContextPropertyValue after trying to adjust the other observers. I'll remove this method and the two tests. The runtime issue is the reason why I can't do the same for the HttpClient Observer(s)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I understand that HttpClientObserver needs to keep using reflection due to the runtime issue. But that doesn't apply to AspNetCoreHostingObserver, so please update to remove the reflection there.

@TimHess TimHess force-pushed the feature/header-masking-httptrace branch 2 times, most recently from c25ef15 to 8d78612 Compare September 18, 2024 22:16
@TimHess TimHess changed the base branch from main to fix-schema-cref-in-para September 18, 2024 22:16
@TimHess
Copy link
Member Author

TimHess commented Sep 18, 2024

/azp run cleanup-code

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link

Code cleanup successfully reformatted files and pushed changes.

@TimHess TimHess marked this pull request as ready for review September 19, 2024 13:48
@TimHess TimHess reopened this Sep 19, 2024
@TimHess TimHess changed the base branch from fix-schema-cref-in-para to main September 19, 2024 14:06
@TimHess
Copy link
Member Author

TimHess commented Sep 19, 2024

/azp run cleanup-code

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link

Code cleanup successfully reformatted files and pushed changes.

@TimHess TimHess force-pushed the feature/header-masking-httptrace branch from df33a65 to 38337b9 Compare September 19, 2024 15:47
Copy link
Member

@bart-vmware bart-vmware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've completed a full sweep of all changes. Please don't rebase anymore until this PR is approved.

return new HttpExchangesResult(recentExchanges);
}

public override void ProcessEvent(string eventName, object? value)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what's going on. The code was updated to type-check for HttpContext as I suggested, and now only unit tests call the method GetHttpContextPropertyValue (so it has basically become redundant). But the runtime issue states we still need to use reflection?

@TimHess
Copy link
Member Author

TimHess commented Sep 23, 2024

/azp run Steeltoe.All

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

1 similar comment
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link

Code cleanup successfully reformatted files and pushed changes.

Comment on lines 25 to 45
[Fact]
public async Task HttpExchangesActuator_DoesNotCaptureAuthInUri()
{
WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create();
builder.Configuration.AddInMemoryCollection(AppSettings);
builder.AddHttpExchangesActuator();
await using WebApplication host = builder.Build();

host.UseRouting();
await host.StartAsync();
using HttpClient httpClient = host.GetTestClient();

var requestUri = new Uri("http://username:password@localhost/actuator/httpexchanges");
_ = await httpClient.GetAsync(requestUri);
HttpResponseMessage response = await httpClient.GetAsync(requestUri);
response.StatusCode.Should().Be(HttpStatusCode.OK);

string json = await response.Content.ReadAsStringAsync();
json.Should().NotContain("username").And.NotContain("password");
json.Should().Contain("http://localhost:80/actuator/httpexchanges");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[Fact]
public async Task HttpExchangesActuator_DoesNotCaptureAuthInUri()
{
WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create();
builder.Configuration.AddInMemoryCollection(AppSettings);
builder.AddHttpExchangesActuator();
await using WebApplication host = builder.Build();
host.UseRouting();
await host.StartAsync();
using HttpClient httpClient = host.GetTestClient();
var requestUri = new Uri("http://username:password@localhost/actuator/httpexchanges");
_ = await httpClient.GetAsync(requestUri);
HttpResponseMessage response = await httpClient.GetAsync(requestUri);
response.StatusCode.Should().Be(HttpStatusCode.OK);
string json = await response.Content.ReadAsStringAsync();
json.Should().NotContain("username").And.NotContain("password");
json.Should().Contain("http://localhost:80/actuator/httpexchanges");
}
[Fact]
public async Task HttpExchangesActuator_DoesNotCaptureAuthInUri()
{
WebApplicationBuilder builder = TestWebApplicationBuilderFactory.CreateDefault(false);
builder.Configuration.AddInMemoryCollection(AppSettings);
builder.AddHttpExchangesActuator();
await using WebApplication host = builder.Build();
host.UseRouting();
host.MapGet("/hello", () => "Hello World!");
await host.StartAsync();
using var httpClient = new HttpClient();
HttpResponseMessage helloResponse = await httpClient.GetAsync("http://username:password@localhost:5000/hello");
helloResponse.StatusCode.Should().Be(HttpStatusCode.OK);
HttpResponseMessage actuatorResponse = await httpClient.GetAsync(new Uri("http://localhost:5000/actuator/httpexchanges"));
actuatorResponse.StatusCode.Should().Be(HttpStatusCode.OK);
var actuatorRootElement = await actuatorResponse.Content.ReadFromJsonAsync<JsonElement>();
JsonElement[] exchangesArray = actuatorRootElement.GetProperty("exchanges").EnumerateArray().ToArray();
exchangesArray.Should().HaveCount(1);
string requestUri = exchangesArray[0].GetProperty("request").GetProperty("uri").ToString();
requestUri.Should().Be("http://localhost:5000/hello");
}

The suggested test is more reliable because:

  • It starts a real server, bypassing short-circuiting from TestServer.
  • It verifies a regular request, instead of an actuator endpoint.
  • It verifies we're actually getting back a response that's not an empty exchanges array.
  • It verifies the URI is correct, not empty/omitted, etc.

Note: Taking this suggestion requires adding using System.Text.Json; at the top.

filteredHeaders.Response.Headers["HeaderA"][0].Should().Be(HttpExchangesDiagnosticObserver.Redacted);
filteredHeaders.Response.Headers["HeaderB"][0].Should().Be("headerBValue");
filteredHeaders.Response.Headers[HeaderNames.SetCookie][0].Should().Be(HttpExchangesDiagnosticObserver.Redacted);
filteredHeaders.Principal?.Name.Should().Be("MyTestName");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never use ?. in FluentAssertions-style assertions. If null, the right-hand side doesn't execute, thus no assertion is performed.

Aside from multiple occurrences in this file, please also correct the ones in ApplicationInstanceCertificateTest.

observer.ProcessEvent("Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop", context);
}

observer.Queue.Count.Should().Be(option.CurrentValue.Capacity);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a poor assertion, because it won't dump the collection contents on failure. The best it can show is "5 should be 4".
Instead, use:

observer.Queue.Should().HaveCount(option.CurrentValue.Capacity);

Applies to multiple places in this file.

{
using var listener = new DiagnosticListener("test");

IOptionsMonitor<HttpExchangesEndpointOptions> option =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rename these vars to optionsMonitor, which is what they are.


public sealed class HttpExchange
{
[JsonPropertyName("TimeTaken")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this Pascal-cased, whereas all other properties aren't?
And this doesn't actually work because the JSON serializer skips internal properties. Test coverage is missing. And apparently, it hasn't been manually tested either.

Having unit tests is no excuse for omitting tests that verify the entire feature works, which was already identified in #1028. What ultimately matters the most is the response being returned, because that's what users observe. So at the minimum, there must be a test that asserts on the JSON returned when all options are turned on, and one for when all options are turned off. As they are still missing (this far in the review process), I wrote them myself in aaf76b6. In doing so, I've identified various bugs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, that was a combination of copy/paste from the property name and being distracted by time stamp in the response without paying close enough attention. Thanks for writing the tests, I pulled in that commit


public sealed class HttpExchange
{
public DateTime Timestamp { get; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still a UTC date/time in the response, not a timestamp in Unix Epoch ticks.

response = await httpClient.GetAsync(new Uri("http://localhost/actuator/httpexchanges"));
response.StatusCode.Should().Be(HttpStatusCode.OK);
string json = await response.Content.ReadAsStringAsync();
json.Should().NotContain("username").And.NotContain("password");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't make sense, there's no username/password anywhere in this test.

Comment on lines 69 to 70
json.Should().Contain("http://localhost:80/actuator/httpexchanges");
json.Should().Contain($"\"timestamp\":\"{DateTime.UtcNow.Date:yyyy-MM-dd}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These assertions are way insufficient, see my other comment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test and the one above now effectively do the same thing but with different assertions, so I'm just going to consolidate them and fill out the assertion set for all of the properties

return new HttpExchangesResult(recentExchanges);
}

public override void ProcessEvent(string eventName, object? value)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I understand that HttpClientObserver needs to keep using reflection due to the runtime issue. But that doesn't apply to AspNetCoreHostingObserver, so please update to remove the reflection there.

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link

sonarcloud bot commented Sep 24, 2024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Component/Management Issues related to Steeltoe Management (actuators) ReleaseLine/4.x Identified as a feature/fix for the 4.x release line Type/enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Authorization Header is shown by Actuator trace endpoint
2 participants