[APMSVLS-442] Adding azure frontdoor support - #8861
Conversation
BenchmarksBenchmark execution time: 2026-07-31 18:23:34 Comparing candidate commit 0b9cd47 in PR branch Found 0 performance improvements and 2 performance regressions! Performance is the same for 70 metrics, 0 unstable metrics, 69 known flaky benchmarks, 57 flaky benchmarks without significant changes.
|
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (8861) and master. ✅ No regressions detected - check the details below Full Metrics ComparisonFakeDbCommand
HttpMessageHandler
Comparison explanationExecution-time benchmarks measure the whole time it takes to execute a program, and are intended to measure the one-off costs. Cases where the execution time results for the PR are worse than latest master results are highlighted in **red**. The following thresholds were used for comparing the execution times:
Note that these results are based on a single point-in-time result for each branch. For full results, see the dashboard. Graphs show the p99 interval based on the mean and StdDev of the test run, as well as the mean value of the run (shown as a diamond below the graph). Duration chartsFakeDbCommand (.NET Framework 4.8)gantt
title Execution time (ms) FakeDbCommand (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8861) - mean (73ms) : 69, 77
master - mean (72ms) : 69, 75
section Bailout
This PR (8861) - mean (75ms) : 74, 77
master - mean (81ms) : 73, 89
section CallTarget+Inlining+NGEN
This PR (8861) - mean (1,093ms) : 1048, 1138
master - mean (1,096ms) : 1052, 1141
FakeDbCommand (.NET Core 3.1)gantt
title Execution time (ms) FakeDbCommand (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8861) - mean (113ms) : 101, 126
master - mean (115ms) : 108, 121
section Bailout
This PR (8861) - mean (111ms) : 109, 113
master - mean (115ms) : 108, 122
section CallTarget+Inlining+NGEN
This PR (8861) - mean (787ms) : 761, 814
master - mean (785ms) : 763, 807
FakeDbCommand (.NET 6)gantt
title Execution time (ms) FakeDbCommand (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8861) - mean (98ms) : 95, 100
master - mean (103ms) : 97, 109
section Bailout
This PR (8861) - mean (98ms) : 95, 101
master - mean (99ms) : 96, 103
section CallTarget+Inlining+NGEN
This PR (8861) - mean (951ms) : 915, 987
master - mean (950ms) : 901, 999
FakeDbCommand (.NET 8)gantt
title Execution time (ms) FakeDbCommand (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8861) - mean (100ms) : 95, 106
master - mean (98ms) : 92, 105
section Bailout
This PR (8861) - mean (98ms) : 95, 101
master - mean (102ms) : 96, 108
section CallTarget+Inlining+NGEN
This PR (8861) - mean (821ms) : 784, 858
master - mean (822ms) : 782, 862
HttpMessageHandler (.NET Framework 4.8)gantt
title Execution time (ms) HttpMessageHandler (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8861) - mean (215ms) : 209, 220
master - mean (211ms) : 205, 217
section Bailout
This PR (8861) - mean (218ms) : 213, 222
master - mean (215ms) : 211, 219
section CallTarget+Inlining+NGEN
This PR (8861) - mean (1,277ms) : 1230, 1324
master - mean (1,268ms) : 1214, 1321
HttpMessageHandler (.NET Core 3.1)gantt
title Execution time (ms) HttpMessageHandler (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8861) - mean (303ms) : 295, 311
master - mean (303ms) : 294, 312
section Bailout
This PR (8861) - mean (306ms) : 299, 313
master - mean (305ms) : 295, 315
section CallTarget+Inlining+NGEN
This PR (8861) - mean (1,009ms) : 986, 1033
master - mean (1,009ms) : 984, 1035
HttpMessageHandler (.NET 6)gantt
title Execution time (ms) HttpMessageHandler (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8861) - mean (299ms) : 290, 309
master - mean (298ms) : 291, 305
section Bailout
This PR (8861) - mean (296ms) : 290, 301
master - mean (297ms) : 291, 303
section CallTarget+Inlining+NGEN
This PR (8861) - mean (1,204ms) : 1164, 1244
master - mean (1,200ms) : 1160, 1239
HttpMessageHandler (.NET 8)gantt
title Execution time (ms) HttpMessageHandler (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8861) - mean (297ms) : 290, 304
master - mean (300ms) : 294, 306
section Bailout
This PR (8861) - mean (300ms) : 293, 307
master - mean (301ms) : 293, 308
section CallTarget+Inlining+NGEN
This PR (8861) - mean (1,105ms) : 1009, 1200
master - mean (1,094ms) : 989, 1199
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 025d9ec808
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
bouwkast
left a comment
There was a problem hiding this comment.
Haven't gone through all of it yet
| { | ||
| try | ||
| { | ||
| var resourceUrl = data.Path is null ? string.Empty : UriHelpers.GetCleanUriPath($"/{data.Path}").ToLowerInvariant(); |
There was a problem hiding this comment.
If I recall correctly data.Path (x-dd-proxy-path) is already an absolute path such as /api/foo
This $"/{data.Path}" will cause it to become //api/foo.
Is that expected?
There was a problem hiding this comment.
So azure doe not prepend the / in the url path for front door -- since this factory is just for azure front door this will not happen.
There was a problem hiding this comment.
Could we update the Front Door test fixture, which currently uses /api/test, and add a comment documenting this contract? It would also be helpful to link the Front Door rule configuration that guarantees the header uses {url_path}.
e.g. this is set
headers.Set(InferredProxyHeaders.Path, "/api/test");
Additionally, it may be best to conditionally add the / if we see it missing we add it if we see it there we skip it.
| { | ||
| try | ||
| { | ||
| var resourceUrl = data.Path is null ? string.Empty : UriHelpers.GetCleanUriPath($"/{data.Path}").ToLowerInvariant(); |
There was a problem hiding this comment.
Could we update the Front Door test fixture, which currently uses /api/test, and add a comment documenting this contract? It would also be helpful to link the Front Door rule configuration that guarantees the header uses {url_path}.
e.g. this is set
headers.Set(InferredProxyHeaders.Path, "/api/test");
Additionally, it may be best to conditionally add the / if we see it missing we add it if we see it there we skip it.
| // this is the local root span | ||
| tags.SetAnalyticsSampleRate(IntegrationId, tracer.CurrentTraceSettings.Settings, enabledWithGlobalSetting: false); | ||
| } | ||
| else |
There was a problem hiding this comment.
I think this will potentially modify an Azure Front Door span that is at the root - is that expected? I'm not sure if it should get these Azure Functions tags applied to it.
There was a problem hiding this comment.
We may want to guard this with else if (rootSpan.OperationName != AzureFrontDoor && rootSpan.OperationName != AzureApim)
There was a problem hiding this comment.
I added guard logic and added a test for this.
Dismissing as the "/" is typically not included for Azure Front door URLs, but tests do seem to include it
I think adding a conditional here would be best however.

Summary of changes
This PR implements Azure FrontDoor. This is an upstream proxy service that Azure offers.
Azure Front Door
Azure Front Door is a edge computing CDN proxy that provides load balancing and regional routing to azure web apps and containers. Customers use this ensure their application has a single entry point across multiple regions.
One special case is that azure front door lacks the ability to insert timestamps in the headers. This may change in the future but due to this I modified the process to add a timespan at header detection which means that the accuracy of the timeline decreases but we still get a usable span and the proxy is represented. If this does change and the ability to insert timestamps does get implemented on Azure's side the code is thoughtful enough to not overwrite an existing timestamp header and making it less of an urgent change to remove the code I am introducing.
Reason for change
This is a proxy service from Azure. It would be great to provide observability coverage for customers to see in their traces.
Implementation details
I added Azure Front Door support to the inferred proxy span pipeline, following the same pattern already established for Azure API Management and AWS API Gateway.
The core of the work is two new classes: AzureFrontDoorExtractor and AzureFrontDoorSpanFactory. The extractor reads the standard inferred proxy headers (start time, domain, HTTP method, path, region) from the incoming request and returns an InferredProxyData struct. Unlike APIM and API Gateway, Azure Front Door does not inject a timestamp header, so if one isn't present the extractor synthesizes a start time from the current time — but if a start time header is present it will be used instead. The span factory takes that data and creates a web-type span with operation name azure.frontdoor, setting resource name, HTTP tags, region, and using the domain name as the service name.
I wired these into InferredProxySpanHelper so that when the x-dd-proxy header value is azure-fd, the coordinator is initialized with the Front Door extractor and factory and produces the inferred proxy scope.
I also updated AzureFunctionsCommon to recognize azure.frontdoor as a proxy operation name — same as it already does for azure.apim — so that Azure Functions doesn't overwrite the inferred proxy span as the root.
Finally, I added the constant AzureFrontDoorName = "azure.frontdoor" to AzureFunctionsConstants, and wrote unit tests for both the extractor and the span factory covering the happy path, missing optional headers, and invalid/missing start time.
Test coverage
AzureFrontDoorExtractorTests has 7 tests total (4 via [Theory], 3 [Fact]):
I verify that when all headers are present and valid, TryExtract returns true and correctly populates ProxyName, StartTime, HttpMethod, Path, Stage, and Region.
I verify that when only the required start time header is present (optional headers removed), extraction still succeeds and the optional fields come back null.
I cover 4 invalid start time cases (null, empty string, non-numeric, and an overflowing value) via a theory — all should return false with a default InferredProxyData.
I verify that a completely missing start time header also returns false.
AzureFrontDoorSpanFactoryTests has 1 test:
I verify that CreateSpan produces a span with the correct operation name (azure.frontdoor), resource name (GET /api/v1/users), span type (web), start time, and all expected tags (http.method, component, http.url, http.route).
Other details