qlog: split serializiation and event definitions, remove logging abstraction (#5356)

* qlog: implement a Trace and a Writer struct

* qlog: rename Trace to FileSeq

* split qlog trace writer and QUIC qlog events into separate packages

* use the new qlog.Recorder instead of the logging.ConnectionTracer
This commit is contained in:
Marten Seemann
2025-10-08 11:53:02 +08:00
committed by GitHub
parent c9d3778fe0
commit c2131eb595
90 changed files with 5146 additions and 7787 deletions

View File

@@ -0,0 +1,90 @@
package events
import (
"reflect"
"slices"
"sync"
"time"
"github.com/quic-go/quic-go/qlogwriter"
)
// Event is a recorded event with the event time.
type Event struct {
Time time.Time
Event qlogwriter.Event
}
// Trace is a qlog.Trace that returns a qlog recorder.
type Trace struct {
Recorder qlogwriter.Recorder
}
var _ qlogwriter.Trace = &Trace{}
func (t *Trace) AddProducer() qlogwriter.Recorder {
return t.Recorder
}
// Recorder is a qlog.Recorder that records events.
// Events can be retrieved using the Events method.
type Recorder struct {
mx sync.Mutex
events []Event
}
var _ qlogwriter.Recorder = &Recorder{}
// Events returns all recorded events.
// If filter is provided, only events of the given type(s) are returned.
func (r *Recorder) RecordEvent(ev qlogwriter.Event) {
r.mx.Lock()
r.events = append(r.events, Event{Time: time.Now(), Event: ev})
r.mx.Unlock()
}
// Events returns all recorded events, including the event time.
// If filter is provided, only events of the given type(s) are returned.
func (r *Recorder) Events(filter ...qlogwriter.Event) []qlogwriter.Event {
eventsWithTime := r.EventsWithTime(filter...)
events := make([]qlogwriter.Event, 0, len(eventsWithTime))
for _, ev := range eventsWithTime {
events = append(events, ev.Event)
}
return events
}
func (r *Recorder) EventsWithTime(filter ...qlogwriter.Event) []Event {
r.mx.Lock()
events := r.events
r.mx.Unlock()
if len(filter) == 0 {
return events
}
// Some events have the same name when serialized, but use different structs.
// We therefore need to filter by type, and can't use the event name.
filterTypes := make([]reflect.Type, 0, len(filter))
for _, f := range filter {
filterTypes = append(filterTypes, reflect.TypeOf(f))
}
var filtered []Event
for _, ev := range events {
eventType := reflect.TypeOf(ev.Event)
if slices.Contains(filterTypes, eventType) {
filtered = append(filtered, ev)
}
}
return filtered
}
// Clear clears the recorded events.
func (r *Recorder) Clear() {
r.mx.Lock()
r.events = nil
r.mx.Unlock()
}
func (r *Recorder) Close() error { return nil }

View File

@@ -0,0 +1,101 @@
package events
import (
"testing"
"time"
"github.com/quic-go/quic-go/internal/synctest"
"github.com/quic-go/quic-go/qlog"
"github.com/quic-go/quic-go/qlogwriter"
"github.com/stretchr/testify/require"
)
func TestRecorder(t *testing.T) {
recorder := &Recorder{}
defer recorder.Close()
recorder.RecordEvent(qlog.MTUUpdated{Value: 1000})
recorder.RecordEvent(qlog.ALPNInformation{ChosenALPN: "foobar"})
recorder.RecordEvent(qlog.ECNStateUpdated{State: qlog.ECNStateCapable})
recorder.RecordEvent(qlog.MTUUpdated{Value: 1200})
require.Equal(t,
[]qlogwriter.Event{
qlog.MTUUpdated{Value: 1000},
qlog.ALPNInformation{ChosenALPN: "foobar"},
qlog.ECNStateUpdated{State: qlog.ECNStateCapable},
qlog.MTUUpdated{Value: 1200},
},
recorder.Events(),
)
require.Empty(t, recorder.Events(qlog.PacketBuffered{}))
require.Equal(t,
[]qlogwriter.Event{
qlog.MTUUpdated{Value: 1000},
qlog.MTUUpdated{Value: 1200},
},
recorder.Events(qlog.MTUUpdated{}),
)
recorder.Clear()
require.Empty(t, recorder.Events())
require.Empty(t, recorder.Events(qlog.MTUUpdated{}))
}
func TestRecorderFilterEventsSameName(t *testing.T) {
// some events have the same name when serialized, but use different structs
require.Equal(t,
qlog.PacketReceived{}.Name(),
qlog.VersionNegotiationReceived{}.Name(),
)
recorder := &Recorder{}
defer recorder.Close()
recorder.RecordEvent(qlog.PacketReceived{
Header: qlog.PacketHeader{PacketType: qlog.PacketTypeHandshake},
})
recorder.RecordEvent(qlog.VersionNegotiationReceived{
Header: qlog.PacketHeaderVersionNegotiation{},
SupportedVersions: []qlog.Version{0xdeadbeef, 0xdecafbad},
})
require.Equal(t,
[]qlogwriter.Event{
qlog.PacketReceived{
Header: qlog.PacketHeader{PacketType: qlog.PacketTypeHandshake},
},
},
recorder.Events(qlog.PacketReceived{}),
)
require.Equal(t,
[]qlogwriter.Event{
qlog.VersionNegotiationReceived{
Header: qlog.PacketHeaderVersionNegotiation{},
SupportedVersions: []qlog.Version{0xdeadbeef, 0xdecafbad},
},
},
recorder.Events(qlog.VersionNegotiationReceived{}),
)
}
func TestRecorderEventsWithTime(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
recorder := &Recorder{}
start := time.Now()
recorder.RecordEvent(qlog.MTUUpdated{Value: 1000})
time.Sleep(time.Minute)
recorder.RecordEvent(qlog.ECNStateUpdated{State: qlog.ECNStateCapable})
time.Sleep(time.Minute)
recorder.RecordEvent(qlog.MTUUpdated{Value: 1200})
require.Equal(t,
[]Event{
{Time: start, Event: qlog.MTUUpdated{Value: 1000}},
{Time: start.Add(2 * time.Minute), Event: qlog.MTUUpdated{Value: 1200}},
},
recorder.EventsWithTime(qlog.MTUUpdated{}),
)
})
}