fix(telemetry): reverse the order of HTTP and gateway user agents#19821
fix(telemetry): reverse the order of HTTP and gateway user agents#19821parametalol wants to merge 4 commits intomasterfrom
Conversation
|
Skipping CI for Draft Pull Request. |
📝 WalkthroughWalkthroughThe changes introduce clearer HTTP header manipulation semantics in the telemetry phonehome module. The Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🚀 Build Images ReadyImages are ready for commit d008914. To use with deploy scripts: export MAIN_IMAGE_TAG=4.11.x-583-gd008914bd3 |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #19821 +/- ##
==========================================
- Coverage 49.60% 49.60% -0.01%
==========================================
Files 2763 2763
Lines 208271 208335 +64
==========================================
+ Hits 103309 103337 +28
- Misses 97294 97331 +37
+ Partials 7668 7667 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
Headers.Set, you can avoid repeatedhttp.Header(h)conversions and an unnecessary delete when no values are provided by early-returning on an emptyvaluesslice and reusing a localhdr := http.Header(h)alias. - The new
Headers.Addhelper is a good addition, but its comment is a bit vague; consider clarifying that it appends values to existing header entries (mirroringhttp.Header.Add) rather than replacing them, to make its behavior immediately clear to readers.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `Headers.Set`, you can avoid repeated `http.Header(h)` conversions and an unnecessary delete when no values are provided by early-returning on an empty `values` slice and reusing a local `hdr := http.Header(h)` alias.
- The new `Headers.Add` helper is a good addition, but its comment is a bit vague; consider clarifying that it appends values to existing header entries (mirroring `http.Header.Add`) rather than replacing them, to make its behavior immediately clear to readers.
## Individual Comments
### Comment 1
<location path="pkg/telemetry/phonehome/interceptor_test.go" line_range="181" />
<code_context>
rp := getGRPCRequestDetails(ctx, err, "ignored grpc method", "request")
s.Equal(http.StatusOK, rp.Code)
- s.Equal([]string{"gateway", "user"}, rp.Headers.Get(userAgentHeaderKey))
+ s.Equal([]string{"user", "gateway"}, rp.Headers.Get(userAgentHeaderKey))
s.Nil(rp.UserID)
s.Equal("request", rp.GRPCReq)
</code_context>
<issue_to_address>
**suggestion (testing):** Add more test coverage for User-Agent combinations and edge cases
This assertion now covers the case where both HTTP and gateway User-Agents are present. To fully validate `getGRPCRequestDetails` behavior, please add tests for:
- Only HTTP User-Agent (no gRPC/gateway metadata)
- Only gRPC/gateway User-Agent (no HTTP header)
- Multiple metadata User-Agent values (verify ordering)
- Neither HTTP nor metadata User-Agent (no header set)
These will better protect against regressions in User-Agent construction across different request shapes.
Suggested implementation:
```golang
rp := getGRPCRequestDetails(ctx, err, "ignored grpc method", "request")
s.Equal(http.StatusOK, rp.Code)
s.Equal([]string{"user", "gateway"}, rp.Headers.Get(userAgentHeaderKey))
s.Nil(rp.UserID)
s.Equal("request", rp.GRPCReq)
s.Equal("/wrapped/http", rp.Path)
func TestGetGRPCRequestDetails_UserAgentVariants(t *testing.T) {
type testCase struct {
name string
md metadata.MD
want []string
}
testCases := []testCase{
{
name: "only HTTP user agent (no gateway user agent)",
md: metadata.Pairs(
"grpcgateway-user-agent", "user",
),
want: []string{"user"},
},
{
name: "only gateway user agent (no HTTP user agent)",
md: metadata.Pairs(
"user-agent", "gateway",
),
want: []string{"gateway"},
},
{
name: "both HTTP and gateway user agents (HTTP first)",
md: metadata.Pairs(
"grpcgateway-user-agent", "user",
"user-agent", "gateway",
),
want: []string{"user", "gateway"},
},
{
name: "multiple HTTP and gateway user agents preserve ordering",
md: metadata.Pairs(
"grpcgateway-user-agent", "user1",
"grpcgateway-user-agent", "user2",
"user-agent", "gateway1",
"user-agent", "gateway2",
),
// Expect all HTTP user agents first, in order, then all gateway user agents, in order.
want: []string{"user1", "user2", "gateway1", "gateway2"},
},
{
name: "no HTTP or gateway user agent (header not set)",
md: metadata.MD{},
want: nil,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
ctx := metadata.NewIncomingContext(context.Background(), tc.md)
rp := getGRPCRequestDetails(ctx, nil, "ignored grpc method", "request")
got := rp.Headers.Get(userAgentHeaderKey)
if tc.want == nil {
require.Nil(t, got)
return
}
require.Equal(t, tc.want, got)
})
}
}
```
To compile and run these tests, ensure the following in `pkg/telemetry/phonehome/interceptor_test.go`:
1. The import block includes:
- `testing`
- `context`
- `github.com/stretchr/testify/require`
- `google.golang.org/grpc/metadata`
For example:
```go
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/metadata"
)
```
2. The metadata keys used here (`"grpcgateway-user-agent"` for the original HTTP User-Agent and `"user-agent"` for the gateway/gRPC client User-Agent) must match what `getGRPCRequestDetails` actually reads. If the function uses different metadata keys, update the `metadata.Pairs` calls accordingly.
3. The expected ordering in the `"multiple HTTP and gateway user agents preserve ordering"` test (`HTTP UAs first, then gateway UAs`) should reflect the logic in `getGRPCRequestDetails`. If the function orders them differently, adjust the `want` slice to match the intended behavior.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
The grpc-gateway forwards the original HTTP User-Agent into gRPC metadata, and the gRPC transport prepends its own. The previous code appended all metadata values unconditionally, duplicating the original. Now only metadata values not already present in the HTTP headers are appended. The test now simulates the real scenario where metadata contains both the forwarded and transport User-Agent values. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cover: HTTP-only, metadata-only, multiple metadata values with ordering, and no User-Agent at all. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5f687ce to
d008914
Compare
Description
This PR refactors the user-agent calculation and btw changes the order of gRPC request user agents in the captured request parameters so that the original HTTP user agent goes first, and the grpc-gateway goes after.
Why important? Not much, but as the values for the header a stored in an array, a RequestParams reader might consider the first value to be the source user-agent.
It also fixes the
Setmethod, that should delete the existing value if no new values are provided.User-facing documentation
Testing and quality
Automated testing
How I validated my change
CI, manual.