Skip to content
This repository was archived by the owner on Apr 30, 2025. It is now read-only.

Commit 085cb3b

Browse files
committed
feat: log local TCP address
When troubleshooting issues related to keepalive it is often useful to be able to correlate multiple requests made over the same connection. A first step was to introduce the information on whether the request was made on a re-used connection or not which was done with a7dbf84. This commit adds the local address of the TCP connection which adds the capability to distinguish between multiple, parallel keep-alive connections by looking at the client port of gorouter for the backend connection.
1 parent a17697c commit 085cb3b

File tree

7 files changed

+77
-7
lines changed

7 files changed

+77
-7
lines changed

accesslog/schema/access_log_record.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ type AccessLogRecord struct {
110110
LogAttemptsDetails bool
111111
FailedAttempts int
112112
RoundTripSuccessful bool
113+
ExtraFields []string
113114
record []byte
114115

115116
// See the handlers.RequestInfo struct for details on these timings.
@@ -124,6 +125,8 @@ type AccessLogRecord struct {
124125
TlsHandshakeFinishedAt time.Time
125126
AppRequestFinishedAt time.Time
126127
FinishedAt time.Time
128+
129+
LocalAddress string
127130
}
128131

129132
func (r *AccessLogRecord) formatStartedAt() string {
@@ -319,6 +322,17 @@ func (r *AccessLogRecord) makeRecord(performTruncate bool) []byte {
319322
b.WriteDashOrFloatValue(r.successfulAttemptTime())
320323
}
321324

325+
// We have to consider the impact of iterating over a list. This technically allows to repeat
326+
// some of the fields but it allows us to iterate over the list only once instead of once per
327+
// field when we perform a [slices.Contains] check.
328+
for _, field := range r.ExtraFields {
329+
switch field {
330+
case "local_address":
331+
b.WriteString(`local_address:`)
332+
b.WriteDashOrStringValue(r.LocalAddress)
333+
}
334+
}
335+
322336
b.AppendSpaces(false)
323337
// #nosec G104 - ignore errors from writing the access log as it will only cause more errors to log this error
324338
b.WriteString(`x_cf_routererror:`)

accesslog/schema/access_log_record_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,43 @@ var _ = Describe("AccessLogRecord", func() {
314314
Eventually(r).Should(Say(`x_cf_routererror:"-"`))
315315
})
316316
})
317+
318+
Context("when extra_fields is set to [local_address]", func() {
319+
Context("and the local address is empty", func() {
320+
It("makes a record with the local address set to -", func() {
321+
record.ExtraFields = []string{"local_address"}
322+
323+
r := BufferReader(bytes.NewBufferString(record.LogMessage()))
324+
Eventually(r).Should(Say(`local_address:"-"`))
325+
})
326+
})
327+
Context("and the local address contains an address", func() {
328+
It("makes a record with the local address set to that address", func() {
329+
record.ExtraFields = []string{"local_address"}
330+
record.LocalAddress = "10.0.0.1:34823"
331+
332+
r := BufferReader(bytes.NewBufferString(record.LogMessage()))
333+
Eventually(r).Should(Say(`local_address:"10.0.0.1:34823"`))
334+
})
335+
})
336+
})
337+
338+
Context("when extra_fields is set to [foobarbazz]", func() {
339+
It("ignores it", func() {
340+
record.ExtraFields = []string{"foobarbazz"}
341+
record.LocalAddress = "10.0.0.1:34823"
342+
343+
r := BufferReader(bytes.NewBufferString(record.LogMessage()))
344+
Consistently(r).ShouldNot(Say("foobarbazz"))
345+
})
346+
It("does not log local_address", func() {
347+
record.ExtraFields = []string{"foobarbazz"}
348+
record.LocalAddress = "10.0.0.1:34823"
349+
350+
r := BufferReader(bytes.NewBufferString(record.LogMessage()))
351+
Consistently(r).ShouldNot(Say(`local_address:"10.0.0.1:34823"`))
352+
})
353+
})
317354
})
318355

319356
Describe("WriteTo", func() {

config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ type LoggingConfig struct {
186186
RedactQueryParams string `yaml:"redact_query_params"`
187187
EnableAttemptsDetails bool `yaml:"enable_attempts_details"`
188188
Format FormatConfig `yaml:"format"`
189+
ExtraFields []string `yaml:"extra_fields"`
189190

190191
// This field is populated by the `Process` function.
191192
JobName string `yaml:"-"`

handlers/access_log.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type accessLog struct {
2020
accessLogger accesslog.AccessLogger
2121
extraHeadersToLog []string
2222
logAttemptsDetails bool
23+
extraFields []string
2324
logger *slog.Logger
2425
}
2526

@@ -46,6 +47,7 @@ func (a *accessLog) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http
4647
Request: r,
4748
ExtraHeadersToLog: a.extraHeadersToLog,
4849
LogAttemptsDetails: a.logAttemptsDetails,
50+
ExtraFields: a.extraFields,
4951
}
5052

5153
requestBodyCounter := &countingReadCloser{delegate: r.Body}
@@ -82,6 +84,8 @@ func (a *accessLog) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http
8284
alr.AppRequestFinishedAt = reqInfo.AppRequestFinishedAt
8385
alr.FinishedAt = reqInfo.FinishedAt
8486

87+
alr.LocalAddress = reqInfo.LocalAddress
88+
8589
a.accessLogger.Log(*alr)
8690
}
8791

handlers/requestinfo.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ type RequestInfo struct {
7171
ShouldRouteToInternalRouteService bool
7272
FailedAttempts int
7373

74+
LocalAddress string
75+
7476
// RoundTripSuccessful will be set once a request has successfully reached a backend instance.
7577
RoundTripSuccessful bool
7678

proxy/round_tripper/proxy_round_tripper.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ func (rt *roundTripper) RoundTrip(originalRequest *http.Request) (*http.Response
191191
slog.Float64("dns-lookup-time", trace.DnsTime()),
192192
slog.Float64("dial-time", trace.DialTime()),
193193
slog.Float64("tls-handshake-time", trace.TlsTime()),
194+
slog.String("local-addr", trace.LocalAddr()),
194195
)
195196

196197
if err != nil {
@@ -255,6 +256,7 @@ func (rt *roundTripper) RoundTrip(originalRequest *http.Request) (*http.Response
255256
slog.Float64("dns-lookup-time", trace.DnsTime()),
256257
slog.Float64("dial-time", trace.DialTime()),
257258
slog.Float64("tls-handshake-time", trace.TlsTime()),
259+
slog.String("local-addr", trace.LocalAddr()),
258260
)
259261

260262
if err != nil {
@@ -347,6 +349,7 @@ func (rt *roundTripper) RoundTrip(originalRequest *http.Request) (*http.Response
347349
reqInfo.DialFinishedAt = trace.DialDone()
348350
reqInfo.TlsHandshakeStartedAt = trace.TlsStart()
349351
reqInfo.TlsHandshakeFinishedAt = trace.TlsDone()
352+
reqInfo.LocalAddress = trace.LocalAddr()
350353

351354
if res != nil && endpoint.PrivateInstanceId != "" && !requestSentToRouteService(request) {
352355
setupStickySession(

proxy/round_tripper/trace.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import (
1111
// requestTracer holds trace data of a single request.
1212
type requestTracer struct {
1313
gotConn atomic.Bool
14-
connInfo atomic.Pointer[httptrace.GotConnInfo]
14+
connReused atomic.Bool
1515
wroteHeaders atomic.Bool
16+
localAddr atomic.Pointer[string]
1617

1718
// all times are stored as returned by time.Time{}.UnixNano()
1819
dnsStart atomic.Int64
@@ -26,8 +27,9 @@ type requestTracer struct {
2627
// Reset the trace data. Helpful when performing the same request again.
2728
func (t *requestTracer) Reset() {
2829
t.gotConn.Store(false)
29-
t.connInfo.Store(nil)
30+
t.connReused.Store(false)
3031
t.wroteHeaders.Store(false)
32+
t.localAddr.Store(nil)
3133
t.dnsStart.Store(0)
3234
t.dnsDone.Store(0)
3335
t.dialStart.Store(0)
@@ -49,11 +51,15 @@ func (t *requestTracer) WroteHeaders() bool {
4951
// ConnReused returns true if the traced request used an idle connection.
5052
// it returns false if no idle connection was used or if the information was unavailable.
5153
func (t *requestTracer) ConnReused() bool {
52-
info := t.connInfo.Load()
53-
if info != nil {
54-
return info.Reused
54+
return t.connReused.Load()
55+
}
56+
57+
func (t *requestTracer) LocalAddr() string {
58+
p := t.localAddr.Load()
59+
if p == nil {
60+
return ""
5561
}
56-
return false
62+
return *p
5763
}
5864

5965
func (t *requestTracer) DnsStart() time.Time {
@@ -121,7 +127,10 @@ func traceRequest(req *http.Request) (*http.Request, *requestTracer) {
121127
r2 := req.WithContext(httptrace.WithClientTrace(req.Context(), &httptrace.ClientTrace{
122128
GotConn: func(info httptrace.GotConnInfo) {
123129
t.gotConn.Store(true)
124-
t.connInfo.Store(&info)
130+
t.connReused.Store(info.Reused)
131+
la := info.Conn.LocalAddr().String()
132+
t.localAddr.Store(&la)
133+
125134
// FIXME: due to https://github.com/golang/go/issues/31259 this breaks our acceptance tests and is dangerous
126135
// disabled for now even though this will reduce the number of requests we can retry
127136
// if !info.Reused {

0 commit comments

Comments
 (0)