Skip to content

Commit b175f8c

Browse files
authored
fix(experimental): Add ldmiddleware library, utilities for creating scoped clients, contexts, events automatically (#306)
Add new `ldmiddleware` library which provides four HTTP middlewares that can help jumpstart a LaunchDarkly setup: * `AddScopedClientForRequest` - creates a scoped client and adds a `ld_request`-kind context to it, an anonymous context populated with various attributes of the incoming HTTP request * `AddScopedClientForRequestWithKeyFn` - same except you provide a custom function for determining the `key` of each request context * `TrackTiming` - track duration metrics using whatever contexts are set so far in the scoped client * `TrackErrorResponses` - track 4xx & 5xx error responses using whatever contexts are set so far in the scoped client See README.md for sample usage. --------- Co-authored-by: Matthew M. Keeler <[email protected]> Release-As: 0.1.0
1 parent 36dad50 commit b175f8c

File tree

10 files changed

+482
-3
lines changed

10 files changed

+482
-3
lines changed

.github/workflows/ldmiddleware-ci.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Build and Test ldmiddleware
2+
permissions:
3+
contents: read
4+
on:
5+
push:
6+
branches: [ 'v7', 'feat/**' ]
7+
paths-ignore:
8+
- '**.md' # Don't run CI on markdown changes.
9+
pull_request:
10+
branches: [ 'v7', 'feat/**' ]
11+
paths-ignore:
12+
- '**.md'
13+
14+
jobs:
15+
go-versions:
16+
uses: ./.github/workflows/go-versions.yml
17+
18+
# Runs the common tasks (unit tests, lint, contract tests) for each Go version.
19+
test-linux:
20+
name: ${{ format('ldmiddleware Linux, Go {0}', matrix.go-version) }}
21+
needs: go-versions
22+
strategy:
23+
# Let jobs fail independently, in case it's a single version that's broken.
24+
fail-fast: false
25+
matrix:
26+
go-version: ${{ fromJSON(needs.go-versions.outputs.matrix) }}
27+
runs-on: ubuntu-latest
28+
steps:
29+
- uses: actions/checkout@v4
30+
- name: Setup Go ${{ inputs.go-version }}
31+
uses: actions/setup-go@v5
32+
with:
33+
go-version: ${{ matrix.go-version }}
34+
- uses: ./.github/actions/unit-tests
35+
with:
36+
lint: 'true'
37+
test-target: ldmiddleware-test
38+
- uses: ./.github/actions/coverage
39+
with:
40+
enforce: 'false'

.release-please-manifest.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
".": "7.13.4",
33
"ldotel": "1.3.0",
4-
"ldai": "0.7.1"
4+
"ldai": "0.7.1",
5+
"ldmiddleware": "0.1.0"
56
}

Makefile

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ COVERAGE_ENFORCER_FLAGS=-package github.com/launchdarkly/go-server-sdk/v7 \
2222
-skipcode "// COVERAGE" \
2323
-packagestats -filestats -showcode
2424

25-
ALL_BUILD_TARGETS=sdk ldotel ldai
25+
ALL_BUILD_TARGETS=sdk ldotel ldai ldmiddleware
2626
ALL_TEST_TARGETS = $(addsuffix -test, $(ALL_BUILD_TARGETS))
2727
ALL_LINT_TARGETS = $(addsuffix -lint, $(ALL_BUILD_TARGETS))
2828

@@ -109,6 +109,32 @@ ldai-lint: $(LINTER_VERSION_FILE)
109109
cd ldai && ../$(LINTER) run .; \
110110
fi
111111

112+
ldmiddleware:
113+
@if [ -f go.work ]; then \
114+
echo "Building ldmiddleware with workspace"; \
115+
go build ./ldmiddleware; \
116+
else \
117+
echo "Building ldmiddleware without workspace"; \
118+
cd ldmiddleware && go build .; \
119+
fi
120+
121+
ldmiddleware-test:
122+
@if [ -f go.work ]; then \
123+
echo "Testing ldmiddleware with workspace"; \
124+
go test -v -race ./ldmiddleware; \
125+
else \
126+
echo "Testing ldmiddleware without workspace"; \
127+
cd ldmiddleware && go test -v -race .; \
128+
fi
129+
130+
ldmiddleware-lint: $(LINTER_VERSION_FILE)
131+
@if [ -f go.work ]; then \
132+
echo "Linting ldmiddleware with workspace"; \
133+
$(LINTER) run ./ldmiddleware; \
134+
else \
135+
echo "Linting ldmiddleware without workspace"; \
136+
cd ldmiddleware && ../$(LINTER) run .; \
137+
fi
112138

113139
test-coverage: $(COVERAGE_PROFILE_RAW)
114140
go run github.com/launchdarkly-labs/go-coverage-enforcer@latest $(COVERAGE_ENFORCER_FLAGS) -outprofile $(COVERAGE_PROFILE_FILTERED) $(COVERAGE_PROFILE_RAW)

ldmiddleware/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
LaunchDarkly HTTP Middleware
2+
===============================
3+
4+
# ⛔️⛔️⛔️⛔️
5+
> [!CAUTION]
6+
> This library is an alpha version and should not be considered ready for production use while this message is visible.
7+
# ☝️☝️☝️☝️☝️☝️
8+
9+
[![Actions Status](https://github.com/launchdarkly/go-server-sdk/actions/workflows/ldmiddleware-ci.yml/badge.svg?branch=v7)](https://github.com/launchdarkly/go-server-sdk/actions/workflows/ldmiddleware-ci.yml)
10+
11+
This package provides a set of HTTP middleware functions that speed up the process of instrumenting your application with LaunchDarkly.
12+
13+
## Usage
14+
15+
```go
16+
import (
17+
ld "github.com/launchdarkly/go-server-sdk/v7"
18+
ldmiddleware "github.com/launchdarkly/go-server-sdk/v7/ldmiddleware"
19+
"net/http"
20+
"time"
21+
"github.com/gorilla/mux"
22+
)
23+
24+
func main() {
25+
client, err := ld.MakeClient("your-sdk-key", 5*time.Second)
26+
if err != nil {
27+
log.Fatal(err)
28+
}
29+
30+
// Add the LaunchDarkly middleware functions to your middleware chain.
31+
// The order of the middleware functions is important.
32+
// The AddScopedClientForRequest function must be called before the TrackTiming and TrackErrorResponses functions.
33+
34+
r := mux.NewRouter()
35+
r.Use(ldmiddleware.AddScopedClientForRequest(client))
36+
r.Use(ldmiddleware.TrackTiming)
37+
r.Use(ldmiddleware.TrackErrorResponses)
38+
39+
r.Handle("/", http.HandlerFunc(myHandler))
40+
http.ListenAndServe(":8080", r)
41+
}
42+
43+
func myHandler(w http.ResponseWriter, r *http.Request) {
44+
// Thanks to `AddScopedClientForRequest`, a scoped client is available in the request context.
45+
// We can use it to evaluate feature flags, track analytics, etc. without having to provide the LaunchDarkly context.
46+
var enableBetaFeatures bool
47+
if client, ok := ld.GetScopedClient(r.Context()); ok {
48+
enableBetaFeatures = client.BoolVariation("your-feature-key", false)
49+
}
50+
51+
if enableBetaFeatures {
52+
// Do something
53+
}
54+
55+
w.WriteHeader(200)
56+
}
57+
```

ldmiddleware/go.mod

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module github.com/launchdarkly/go-server-sdk/ldmiddleware
2+
3+
go 1.23.0
4+
5+
require (
6+
github.com/felixge/httpsnoop v1.0.4
7+
github.com/google/uuid v1.1.1
8+
github.com/launchdarkly/go-sdk-common/v3 v3.4.0
9+
github.com/launchdarkly/go-server-sdk/v7 v7.13.4
10+
github.com/stretchr/testify v1.9.0
11+
)
12+
13+
require (
14+
github.com/davecgh/go-spew v1.1.1 // indirect
15+
github.com/gregjones/httpcache v0.0.0-20171119193500-2bcd89a1743f // indirect
16+
github.com/josharian/intern v1.0.0 // indirect
17+
github.com/launchdarkly/ccache v1.1.0 // indirect
18+
github.com/launchdarkly/eventsource v1.10.0 // indirect
19+
github.com/launchdarkly/go-jsonstream/v3 v3.1.0 // indirect
20+
github.com/launchdarkly/go-sdk-events/v3 v3.5.0 // indirect
21+
github.com/launchdarkly/go-semver v1.0.3 // indirect
22+
github.com/launchdarkly/go-server-sdk-evaluation/v3 v3.0.1 // indirect
23+
github.com/mailru/easyjson v0.7.7 // indirect
24+
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
25+
github.com/pmezard/go-difflib v1.0.0 // indirect
26+
golang.org/x/sync v0.8.0 // indirect
27+
gopkg.in/yaml.v3 v3.0.1 // indirect
28+
)

ldmiddleware/go.sum

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
5+
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
6+
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
7+
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
8+
github.com/gregjones/httpcache v0.0.0-20171119193500-2bcd89a1743f h1:kOkUP6rcVVqC+KlKKENKtgfFfJyDySYhqL9srXooghY=
9+
github.com/gregjones/httpcache v0.0.0-20171119193500-2bcd89a1743f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
10+
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
11+
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
12+
github.com/karlseguin/expect v1.0.2-0.20190806010014-778a5f0c6003 h1:vJ0Snvo+SLMY72r5J4sEfkuE7AFbixEP2qRbEcum/wA=
13+
github.com/karlseguin/expect v1.0.2-0.20190806010014-778a5f0c6003/go.mod h1:zNBxMY8P21owkeogJELCLeHIt+voOSduHYTFUbwRAV8=
14+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
15+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
16+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
17+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
18+
github.com/launchdarkly/ccache v1.1.0 h1:voD1M+ZJXR3MREOKtBwgTF9hYHl1jg+vFKS/+VAkR2k=
19+
github.com/launchdarkly/ccache v1.1.0/go.mod h1:TlxzrlnzvYeXiLHmesMuvoZetu4Z97cV1SsdqqBJi1Q=
20+
github.com/launchdarkly/eventsource v1.10.0 h1:H9Tp6AfGu/G2qzBJC26iperrvwhzdbiA/gx7qE2nDFI=
21+
github.com/launchdarkly/eventsource v1.10.0/go.mod h1:J3oa50bPvJesZqNAJtb5btSIo5N6roDWhiAS3IpsKck=
22+
github.com/launchdarkly/go-jsonstream/v3 v3.1.0 h1:U/7/LplZO72XefBQ+FzHf6o4FwLHVqBE+4V58Ornu/E=
23+
github.com/launchdarkly/go-jsonstream/v3 v3.1.0/go.mod h1:2Pt4BR5AwWgsuVTCcIpB6Os04JFIKWfoA+7faKkZB5E=
24+
github.com/launchdarkly/go-sdk-common/v3 v3.4.0 h1:GTRulE0G43xdWY1QdjAXJ7QnZ8PMFU8pOWZICCydEtM=
25+
github.com/launchdarkly/go-sdk-common/v3 v3.4.0/go.mod h1:6MNeeP8b2VtsM6I3TbShCHW/+tYh2c+p5dB+ilS69sg=
26+
github.com/launchdarkly/go-sdk-events/v3 v3.5.0 h1:Yav8Thm70dZbO8U1foYwZPf3w60n/lNBRaYeeNM/qg4=
27+
github.com/launchdarkly/go-sdk-events/v3 v3.5.0/go.mod h1:oepYWQ2RvvjfL2WxkE1uJJIuRsIMOP4WIVgUpXRPcNI=
28+
github.com/launchdarkly/go-semver v1.0.3 h1:agIy/RN3SqeQDIfKkl+oFslEdeIs7pgsJBs3CdCcGQM=
29+
github.com/launchdarkly/go-semver v1.0.3/go.mod h1:xFmMwXba5Mb+3h72Z+VeSs9ahCvKo2QFUTHRNHVqR28=
30+
github.com/launchdarkly/go-server-sdk-evaluation/v3 v3.0.1 h1:rTgcYAFraGFj7sBMB2b7JCYCm0b9kph4FaMX02t4osQ=
31+
github.com/launchdarkly/go-server-sdk-evaluation/v3 v3.0.1/go.mod h1:fPS5d+zOsgFnMunj+Ki6jjlZtFvo4h9iNbtNXxzYn58=
32+
github.com/launchdarkly/go-server-sdk/v7 v7.13.4 h1:Jn4HQDkmV0DhbUKLz7gFbNrhVrE3xSx8D6FTKEDheis=
33+
github.com/launchdarkly/go-server-sdk/v7 v7.13.4/go.mod h1:EEUSX/bc1mVq+3pwrRzTfu8LFRWRI1UL4XMgzsKWmbE=
34+
github.com/launchdarkly/go-test-helpers/v3 v3.1.0 h1:E3bxJMzMoA+cJSF3xxtk2/chr1zshl1ZWa0/oR+8bvg=
35+
github.com/launchdarkly/go-test-helpers/v3 v3.1.0/go.mod h1:Ake5+hZFS/DmIGKx/cizhn5W9pGA7pplcR7xCxWiLIo=
36+
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
37+
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
38+
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
39+
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
40+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
41+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
42+
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
43+
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
44+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
45+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
46+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
47+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
48+
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ=
49+
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
50+
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
51+
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
52+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
53+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
54+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
55+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
56+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
57+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

ldmiddleware/http_middleware.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package ldmiddleware
2+
3+
import (
4+
"net/http"
5+
"time"
6+
7+
"github.com/felixge/httpsnoop"
8+
"github.com/google/uuid"
9+
10+
"github.com/launchdarkly/go-sdk-common/v3/ldcontext"
11+
"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
12+
ld "github.com/launchdarkly/go-server-sdk/v7"
13+
)
14+
15+
// RequestKeyFunc allows callers to override the request context key for the LDContext.
16+
// Return (key, true) to use the provided key; return ("", false) to fall back to the default UUID key.
17+
type RequestKeyFunc func(r *http.Request) (string, bool)
18+
19+
// AddScopedClientForRequest returns a net/http middleware that, for each incoming request,
20+
// creates an LDScopedClient seeded with a `request`-kind LDContext populated with useful
21+
// HTTP request attributes (e.g., method, path, host, userAgent), and stores it in the
22+
// request's Go context. Downstream handlers can retrieve it via ld.GetScopedClient.
23+
func AddScopedClientForRequest(client *ld.LDClient) func(next http.Handler) http.Handler {
24+
return AddScopedClientForRequestWithKeyFn(client, nil)
25+
}
26+
27+
// AddScopedClientForRequestWithKeyFn is like AddScopedClientForRequest, but allows providing a function to override
28+
// the context key used for the `request`-kind LDContext. If the function returns ok=false or an empty key,
29+
// a random UUID will be used.
30+
func AddScopedClientForRequestWithKeyFn(
31+
client *ld.LDClient, keyFn RequestKeyFunc,
32+
) func(next http.Handler) http.Handler {
33+
return func(next http.Handler) http.Handler {
34+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
35+
// Determine request context key
36+
requestKey := ""
37+
if keyFn != nil {
38+
if k, ok := keyFn(r); ok && k != "" {
39+
requestKey = k
40+
}
41+
}
42+
if requestKey == "" {
43+
requestKey = uuid.New().String()
44+
}
45+
46+
b := ldcontext.NewBuilder(requestKey).Kind("ld_request").Anonymous(true)
47+
b.SetString("method", r.Method)
48+
b.SetString("host", r.Host)
49+
b.SetString("userAgent", r.UserAgent())
50+
if r.URL != nil {
51+
b.SetString("path", r.URL.Path)
52+
b.SetString("scheme", r.URL.Scheme)
53+
b.SetString("query", r.URL.RawQuery)
54+
}
55+
b.SetString("proto", r.Proto)
56+
b.SetString("remoteAddr", r.RemoteAddr)
57+
requestCtx := b.Build()
58+
59+
scoped := ld.NewScopedClient(client, requestCtx)
60+
ctxWithScoped := ld.GoContextWithScopedClient(r.Context(), scoped)
61+
62+
next.ServeHTTP(w, r.WithContext(ctxWithScoped))
63+
})
64+
}
65+
}
66+
67+
// TrackTiming sends a LD event "http.request.duration_ms" with the duration of the request in milliseconds.
68+
// This middleware must be after AddScopedClientForRequest in the middleware chain, as it uses the scoped client
69+
// from the Go context.
70+
//
71+
// The timing event will include all LaunchDarkly contexts added to the scoped client. You may add more
72+
// contexts to the scoped client _during_ the request, and they will be included in the timing event sent
73+
// when the request completes.
74+
func TrackTiming(next http.Handler) http.Handler {
75+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
76+
startTime := time.Now()
77+
next.ServeHTTP(w, r)
78+
duration := time.Since(startTime)
79+
scoped, ok := ld.GetScopedClient(r.Context())
80+
if !ok {
81+
return
82+
}
83+
_ = scoped.TrackMetric("http.request.duration_ms", float64(duration.Milliseconds()), ldvalue.Null())
84+
})
85+
}
86+
87+
// TrackErrorResponses sends a LD event "http.response.4xx" or "http.response.5xx" if the response code is 4xx or 5xx.
88+
// This middleware must be after AddScopedClientForRequest in the middleware chain, as it uses the scoped client
89+
// from the Go context.
90+
//
91+
// The error event will include all LaunchDarkly contexts added to the scoped client. You may add more
92+
// contexts to the scoped client _during_ the request, and they will be included in the error event sent
93+
// when the request completes.
94+
func TrackErrorResponses(next http.Handler) http.Handler {
95+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
96+
metrics := httpsnoop.CaptureMetrics(next, w, r)
97+
if metrics.Code < 400 {
98+
return
99+
}
100+
scoped, ok := ld.GetScopedClient(r.Context())
101+
if !ok {
102+
return
103+
}
104+
if metrics.Code < 500 {
105+
_ = scoped.TrackEvent("http.response.4xx")
106+
return
107+
}
108+
_ = scoped.TrackEvent("http.response.5xx")
109+
})
110+
}

0 commit comments

Comments
 (0)