Add parsing logic for User-Agent headers (#5645)

* feat(ts_telemetry): add parsing logic for user agent headers

* feat(ts_telemetry): update gRPC observability for user-agent headers

* feat(ts_telemetry): add observation tags

* feat(ts_telemetry): Resolved comments

* feat(ts_telemetry): Removed loop variable capture

* feat(ts_telemetry): Updated userAgentKey to be inline

* feat(ts_telemetry): Add support for userAgent as per RFC 9110

* feat(ts_telemetry): Resolved comments

* Fix newline

* feat(ts_telemetry): Moved user-agent parsing to interceptor

* feat(ts_telemetry): Added user-agent parsing complex test case

* Update userAgentsKey comment

Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>

* feat(ts_telemetry): Updated changelog

* feat(ts_telemetry): Resolved comments

---------

Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
This commit is contained in:
Aditya SharmaandJohan Brandhorst-Satzkorn authored and GitHub committed 2025-04-17 23:46:25 +05:30
1 parent a833981eae
commit 30e3116156
14 files changed
+279 -8

No files matched your search

@@ -874,6 +874,114 @@ func Test_statusCodeInterceptor(t *testing.T) {
}
}
func Test_parseUserAgents(t *testing.T) {
t.Parallel()
tests := []struct {
name string
rawUserAgent string
expected []*event.UserAgent
}{
{
name: "valid single user-agent",
rawUserAgent: "Boundary-client-agent/0.1.4",
expected: []*event.UserAgent{
{
Product: "Boundary-client-agent",
ProductVersion: "0.1.4",
},
},
},
{
name: "multiple valid agents with comments",
rawUserAgent: "Boundary-client-agent/0.1.4 (foo; bar); AnotherApp/2.0.0 (baz )",
expected: []*event.UserAgent{
{
Product: "Boundary-client-agent",
ProductVersion: "0.1.4",
Comments: []string{"foo", "bar"},
},
{
Product: "AnotherApp",
ProductVersion: "2.0.0",
Comments: []string{"baz"},
},
},
},
{
name: "complex but valid user agents",
rawUserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 surveyon/2.9.5 (iPhone; CPU iPhone OS 12_5_7 like Mac OS X)",
expected: []*event.UserAgent{
{
Product: "Mozilla",
ProductVersion: "5.0",
Comments: []string{"Macintosh", "Intel Mac OS X 10_15_7"},
},
{
Product: "AppleWebKit",
ProductVersion: "537.36",
Comments: []string{"KHTML, like Gecko"},
},
{
Product: "Chrome",
ProductVersion: "87.0.4280.88",
},
{
Product: "Safari",
ProductVersion: "537.36",
},
{
Product: "surveyon",
ProductVersion: "2.9.5",
Comments: []string{"iPhone", "CPU iPhone OS 12_5_7 like Mac OS X"},
},
},
},
{
name: "invalid client-agent version format (starts with 'v')",
rawUserAgent: "Boundary-client-agent/v0.1.4",
expected: nil,
},
{
name: "invalid client-agent version format (non-semver)",
rawUserAgent: "Boundary-client-agent/0.1.x",
expected: nil,
},
{
name: "empty user-agent",
rawUserAgent: "",
expected: nil,
},
{
name: "valid non client-agent user-agent",
rawUserAgent: "SomeOtherApp/v1.2.3",
expected: []*event.UserAgent{
{
Product: "SomeOtherApp",
ProductVersion: "v1.2.3",
},
},
},
{
name: "mixed valid and invalid agents",
rawUserAgent: "Boundary-client-agent/0.1.4 NoVersionApp SomeOtherApp/",
expected: []*event.UserAgent{
{
Product: "Boundary-client-agent",
ProductVersion: "0.1.4",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
result := parseUserAgents(tt.rawUserAgent)
assert.ElementsMatch(t, tt.expected, result)
})
}
}
func Test_workerRequestInfoInterceptor(t *testing.T) {
factoryCtx := context.Background()
requestCtx := context.Background()