Skip to content

Commit 4930f90

Browse files
qlog: rework the ConnectionClosed event (#5417)
1 parent a6de3e4 commit 4930f90

File tree

5 files changed

+223
-89
lines changed

5 files changed

+223
-89
lines changed

connection.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2174,23 +2174,38 @@ func (c *Conn) handleCloseError(closeErr *closeError) {
21742174
transportErr *TransportError
21752175
)
21762176
var isRemoteClose bool
2177+
var trigger qlog.ConnectionCloseTrigger
2178+
var reason string
2179+
var transportErrorCode *qlog.TransportErrorCode
2180+
var applicationErrorCode *qlog.ApplicationErrorCode
21772181
switch {
21782182
case errors.Is(e, qerr.ErrIdleTimeout),
2179-
errors.Is(e, qerr.ErrHandshakeTimeout),
2180-
errors.As(e, &statelessResetErr),
2181-
errors.As(e, &versionNegotiationErr),
2182-
errors.As(e, &recreateErr):
2183+
errors.Is(e, qerr.ErrHandshakeTimeout):
2184+
trigger = qlog.ConnectionCloseTriggerIdleTimeout
2185+
case errors.As(e, &statelessResetErr):
2186+
trigger = qlog.ConnectionCloseTriggerStatelessReset
2187+
case errors.As(e, &versionNegotiationErr):
2188+
trigger = qlog.ConnectionCloseTriggerVersionMismatch
2189+
case errors.As(e, &recreateErr):
21832190
case errors.As(e, &applicationErr):
21842191
isRemoteClose = applicationErr.Remote
2192+
reason = applicationErr.ErrorMessage
2193+
applicationErrorCode = &applicationErr.ErrorCode
21852194
case errors.As(e, &transportErr):
21862195
isRemoteClose = transportErr.Remote
2196+
reason = transportErr.ErrorMessage
2197+
transportErrorCode = &transportErr.ErrorCode
21872198
case closeErr.immediate:
21882199
e = closeErr.err
21892200
default:
2190-
e = &qerr.TransportError{
2201+
te := &qerr.TransportError{
21912202
ErrorCode: qerr.InternalError,
21922203
ErrorMessage: e.Error(),
21932204
}
2205+
e = te
2206+
reason = te.ErrorMessage
2207+
code := te.ErrorCode
2208+
transportErrorCode = &code
21942209
}
21952210

21962211
c.streamsMap.CloseWithError(e)
@@ -2205,7 +2220,17 @@ func (c *Conn) handleCloseError(closeErr *closeError) {
22052220
defer c.connIDManager.Close()
22062221

22072222
if c.qlogger != nil && !errors.As(e, &recreateErr) {
2208-
c.qlogger.RecordEvent(qlog.ConnectionClosed{Error: e})
2223+
initiator := qlog.InitiatorLocal
2224+
if isRemoteClose {
2225+
initiator = qlog.InitiatorRemote
2226+
}
2227+
c.qlogger.RecordEvent(qlog.ConnectionClosed{
2228+
Initiator: initiator,
2229+
ConnectionError: transportErrorCode,
2230+
ApplicationError: applicationErrorCode,
2231+
Trigger: trigger,
2232+
Reason: reason,
2233+
})
22092234
}
22102235

22112236
// If this is a remote close we're done here

connection_test.go

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -313,8 +313,24 @@ func testConnectionClose(t *testing.T, useApplicationClose bool, expectedErr err
313313

314314
synctest.Wait()
315315

316+
var want qlog.ConnectionClosed
317+
if useApplicationClose {
318+
code := expectedErr.(*qerr.ApplicationError).ErrorCode
319+
want = qlog.ConnectionClosed{
320+
Initiator: qlog.InitiatorLocal,
321+
ApplicationError: &code,
322+
Reason: expectedErr.(*qerr.ApplicationError).ErrorMessage,
323+
}
324+
} else {
325+
code := expectedErr.(*qerr.TransportError).ErrorCode
326+
want = qlog.ConnectionClosed{
327+
Initiator: qlog.InitiatorLocal,
328+
ConnectionError: &code,
329+
Reason: expectedErr.(*qerr.TransportError).ErrorMessage,
330+
}
331+
}
316332
require.Equal(t,
317-
[]qlogwriter.Event{qlog.ConnectionClosed{Error: expectedErr}},
333+
[]qlogwriter.Event{want},
318334
eventRecorder.Events(qlog.ConnectionClosed{}),
319335
)
320336
eventRecorder.Clear()
@@ -346,7 +362,7 @@ func TestConnectionStatelessReset(t *testing.T) {
346362
synctest.Wait()
347363

348364
require.Equal(t,
349-
[]qlogwriter.Event{qlog.ConnectionClosed{Error: &StatelessResetError{}}},
365+
[]qlogwriter.Event{qlog.ConnectionClosed{Initiator: qlog.InitiatorLocal, Trigger: qlog.ConnectionCloseTriggerStatelessReset}},
350366
eventRecorder.Events(qlog.ConnectionClosed{}),
351367
)
352368
})
@@ -936,8 +952,15 @@ func TestConnectionRemoteClose(t *testing.T) {
936952
t.Fatal("timeout")
937953
}
938954

955+
code := expectedErr.ErrorCode
939956
require.Equal(t,
940-
[]qlogwriter.Event{qlog.ConnectionClosed{Error: expectedErr}},
957+
[]qlogwriter.Event{
958+
qlog.ConnectionClosed{
959+
Initiator: qlog.InitiatorRemote,
960+
ConnectionError: &code,
961+
Reason: expectedErr.ErrorMessage,
962+
},
963+
},
941964
eventRecorder.Events(qlog.ConnectionClosed{}),
942965
)
943966
})
@@ -971,7 +994,12 @@ func TestConnectionIdleTimeoutDuringHandshake(t *testing.T) {
971994
}
972995

973996
require.Equal(t,
974-
[]qlogwriter.Event{qlog.ConnectionClosed{Error: &IdleTimeoutError{}}},
997+
[]qlogwriter.Event{
998+
qlog.ConnectionClosed{
999+
Initiator: qlog.InitiatorLocal,
1000+
Trigger: qlog.ConnectionCloseTriggerIdleTimeout,
1001+
},
1002+
},
9751003
eventRecorder.Events(qlog.ConnectionClosed{}),
9761004
)
9771005
})
@@ -1003,7 +1031,12 @@ func TestConnectionHandshakeIdleTimeout(t *testing.T) {
10031031
}
10041032

10051033
require.Equal(t,
1006-
[]qlogwriter.Event{qlog.ConnectionClosed{Error: &HandshakeTimeoutError{}}},
1034+
[]qlogwriter.Event{
1035+
qlog.ConnectionClosed{
1036+
Initiator: qlog.InitiatorLocal,
1037+
Trigger: qlog.ConnectionCloseTriggerIdleTimeout,
1038+
},
1039+
},
10071040
eventRecorder.Events(qlog.ConnectionClosed{}),
10081041
)
10091042
})
@@ -2763,7 +2796,10 @@ func TestConnectionVersionNegotiationNoMatch(t *testing.T) {
27632796
},
27642797
SupportedVersions: vnpVersions,
27652798
},
2766-
qlog.ConnectionClosed{Error: verr},
2799+
qlog.ConnectionClosed{
2800+
Initiator: qlog.InitiatorLocal,
2801+
Trigger: qlog.ConnectionCloseTriggerVersionMismatch,
2802+
},
27672803
},
27682804
eventRecorder.Events(qlog.VersionNegotiationReceived{}, qlog.ConnectionClosed{}),
27692805
)
@@ -3150,9 +3186,14 @@ func TestConnectionEarlyClose(t *testing.T) {
31503186
case err := <-errChan:
31513187
require.Error(t, err)
31523188
require.ErrorContains(t, err, "early error")
3189+
code := qerr.InternalError
31533190
require.Equal(t,
31543191
[]qlogwriter.Event{
3155-
qlog.ConnectionClosed{Error: &qerr.TransportError{ErrorCode: qerr.InternalError, ErrorMessage: "early error"}},
3192+
qlog.ConnectionClosed{
3193+
Initiator: qlog.InitiatorLocal,
3194+
ConnectionError: &code,
3195+
Reason: "early error",
3196+
},
31563197
},
31573198
eventRecorder.Events(qlog.ConnectionClosed{}),
31583199
)

qlog/event.go

Lines changed: 67 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package qlog
22

33
import (
4-
"errors"
54
"fmt"
65
"net/netip"
76
"time"
@@ -130,63 +129,83 @@ func (e VersionInformation) Encode(enc *jsontext.Encoder, _ time.Time) error {
130129
}
131130

132131
type ConnectionClosed struct {
133-
Error error
132+
Initiator Initiator
133+
134+
ConnectionError *TransportErrorCode
135+
ApplicationError *ApplicationErrorCode
136+
137+
Reason string
138+
139+
Trigger ConnectionCloseTrigger
134140
}
135141

136142
func (e ConnectionClosed) Name() string { return "transport:connection_closed" }
137143

138144
func (e ConnectionClosed) Encode(enc *jsontext.Encoder, _ time.Time) error {
139145
h := encoderHelper{enc: enc}
140146
h.WriteToken(jsontext.BeginObject)
141-
var (
142-
statelessResetErr *qerr.StatelessResetError
143-
handshakeTimeoutErr *qerr.HandshakeTimeoutError
144-
idleTimeoutErr *qerr.IdleTimeoutError
145-
applicationErr *qerr.ApplicationError
146-
transportErr *qerr.TransportError
147-
versionNegotiationErr *qerr.VersionNegotiationError
148-
)
149-
switch {
150-
case errors.As(e.Error, &statelessResetErr):
151-
h.WriteToken(jsontext.String("initiator"))
152-
h.WriteToken(jsontext.String(string(InitiatorRemote)))
153-
h.WriteToken(jsontext.String("trigger"))
154-
h.WriteToken(jsontext.String("stateless_reset"))
155-
case errors.As(e.Error, &handshakeTimeoutErr):
156-
h.WriteToken(jsontext.String("initiator"))
157-
h.WriteToken(jsontext.String(string(InitiatorLocal)))
158-
h.WriteToken(jsontext.String("trigger"))
159-
h.WriteToken(jsontext.String("handshake_timeout"))
160-
case errors.As(e.Error, &idleTimeoutErr):
161-
h.WriteToken(jsontext.String("initiator"))
162-
h.WriteToken(jsontext.String(string(InitiatorLocal)))
163-
h.WriteToken(jsontext.String("trigger"))
164-
h.WriteToken(jsontext.String("idle_timeout"))
165-
case errors.As(e.Error, &applicationErr):
166-
initiator := InitiatorLocal
167-
if applicationErr.Remote {
168-
initiator = InitiatorRemote
169-
}
170-
h.WriteToken(jsontext.String("initiator"))
171-
h.WriteToken(jsontext.String(string(initiator)))
172-
h.WriteToken(jsontext.String("application_code"))
173-
h.WriteToken(jsontext.Uint(uint64(applicationErr.ErrorCode)))
174-
h.WriteToken(jsontext.String("reason"))
175-
h.WriteToken(jsontext.String(applicationErr.ErrorMessage))
176-
case errors.As(e.Error, &transportErr):
177-
initiator := InitiatorLocal
178-
if transportErr.Remote {
179-
initiator = InitiatorRemote
147+
h.WriteToken(jsontext.String("initiator"))
148+
h.WriteToken(jsontext.String(string(e.Initiator)))
149+
if e.ConnectionError != nil {
150+
h.WriteToken(jsontext.String("connection_error"))
151+
if e.ConnectionError.IsCryptoError() {
152+
h.WriteToken(jsontext.String(fmt.Sprintf("crypto_error_%#x", uint16(*e.ConnectionError))))
153+
} else {
154+
switch *e.ConnectionError {
155+
case qerr.NoError:
156+
h.WriteToken(jsontext.String("no_error"))
157+
case qerr.InternalError:
158+
h.WriteToken(jsontext.String("internal_error"))
159+
case qerr.ConnectionRefused:
160+
h.WriteToken(jsontext.String("connection_refused"))
161+
case qerr.FlowControlError:
162+
h.WriteToken(jsontext.String("flow_control_error"))
163+
case qerr.StreamLimitError:
164+
h.WriteToken(jsontext.String("stream_limit_error"))
165+
case qerr.StreamStateError:
166+
h.WriteToken(jsontext.String("stream_state_error"))
167+
case qerr.FinalSizeError:
168+
h.WriteToken(jsontext.String("final_size_error"))
169+
case qerr.FrameEncodingError:
170+
h.WriteToken(jsontext.String("frame_encoding_error"))
171+
case qerr.TransportParameterError:
172+
h.WriteToken(jsontext.String("transport_parameter_error"))
173+
case qerr.ConnectionIDLimitError:
174+
h.WriteToken(jsontext.String("connection_id_limit_error"))
175+
case qerr.ProtocolViolation:
176+
h.WriteToken(jsontext.String("protocol_violation"))
177+
case qerr.InvalidToken:
178+
h.WriteToken(jsontext.String("invalid_token"))
179+
case qerr.ApplicationErrorErrorCode:
180+
h.WriteToken(jsontext.String("application_error"))
181+
case qerr.CryptoBufferExceeded:
182+
h.WriteToken(jsontext.String("crypto_buffer_exceeded"))
183+
case qerr.KeyUpdateError:
184+
h.WriteToken(jsontext.String("key_update_error"))
185+
case qerr.AEADLimitReached:
186+
h.WriteToken(jsontext.String("aead_limit_reached"))
187+
case qerr.NoViablePathError:
188+
h.WriteToken(jsontext.String("no_viable_path"))
189+
default:
190+
h.WriteToken(jsontext.String("unknown"))
191+
h.WriteToken(jsontext.String("error_code"))
192+
h.WriteToken(jsontext.Uint(uint64(*e.ConnectionError)))
193+
}
180194
}
181-
h.WriteToken(jsontext.String("initiator"))
182-
h.WriteToken(jsontext.String(string(initiator)))
183-
h.WriteToken(jsontext.String("connection_code"))
184-
h.WriteToken(jsontext.String(transportError(transportErr.ErrorCode).String()))
195+
}
196+
if e.ApplicationError != nil {
197+
h.WriteToken(jsontext.String("application_error"))
198+
h.WriteToken(jsontext.String("unknown"))
199+
h.WriteToken(jsontext.String("error_code"))
200+
h.WriteToken(jsontext.Uint(uint64(*e.ApplicationError)))
201+
}
202+
if e.ConnectionError != nil || e.ApplicationError != nil {
185203
h.WriteToken(jsontext.String("reason"))
186-
h.WriteToken(jsontext.String(transportErr.ErrorMessage))
187-
case errors.As(e.Error, &versionNegotiationErr):
204+
h.WriteToken(jsontext.String(e.Reason))
205+
}
206+
if e.Trigger != "" {
188207
h.WriteToken(jsontext.String("trigger"))
189-
h.WriteToken(jsontext.String("version_mismatch"))
208+
h.WriteToken(jsontext.String(string(e.Trigger)))
190209
}
191210
h.WriteToken(jsontext.EndObject)
192211
return h.err

0 commit comments

Comments
 (0)