qlogwriter: add support for event_schemas in the trace header (#5361)

This commit is contained in:
Marten Seemann
2025-10-09 12:07:23 +08:00
committed by GitHub
parent 33af12712e
commit c26e86c547
7 changed files with 62 additions and 10 deletions

View File

@@ -11,6 +11,7 @@ import (
"github.com/quic-go/quic-go" "github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/internal/utils" "github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/qlog"
"github.com/quic-go/quic-go/qlogwriter" "github.com/quic-go/quic-go/qlogwriter"
) )
@@ -41,7 +42,12 @@ func NewQlogConnectionTracer(logger io.Writer) func(ctx context.Context, isClien
log.Fatalf("failed to create qlog file: %s", err) log.Fatalf("failed to create qlog file: %s", err)
return nil return nil
} }
fileSeq := qlogwriter.NewConnectionFileSeq(utils.NewBufferedWriteCloser(bufio.NewWriter(f), f), isClient, connID) fileSeq := qlogwriter.NewConnectionFileSeq(
utils.NewBufferedWriteCloser(bufio.NewWriter(f), f),
isClient,
connID,
[]string{qlog.EventSchema},
)
go fileSeq.Run() go fileSeq.Run()
return fileSeq return fileSeq
} }

View File

@@ -11,6 +11,7 @@ import (
"github.com/quic-go/quic-go" "github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/internal/utils" "github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/qlog"
"github.com/quic-go/quic-go/qlogwriter" "github.com/quic-go/quic-go/qlogwriter"
) )
@@ -45,7 +46,12 @@ func NewQLOGConnectionTracer(_ context.Context, isClient bool, connID quic.Conne
return nil return nil
} }
log.Printf("Created qlog file: %s\n", path) log.Printf("Created qlog file: %s\n", path)
fileSeq := qlogwriter.NewConnectionFileSeq(utils.NewBufferedWriteCloser(bufio.NewWriter(f), f), isClient, connID) fileSeq := qlogwriter.NewConnectionFileSeq(
utils.NewBufferedWriteCloser(bufio.NewWriter(f), f),
isClient,
connID,
[]string{qlog.EventSchema},
)
go fileSeq.Run() go fileSeq.Run()
return fileSeq return fileSeq
} }

View File

@@ -28,6 +28,7 @@ func BenchmarkConnectionTracing(b *testing.B) {
nopWriteCloser(io.Discard), nopWriteCloser(io.Discard),
false, false,
protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}), protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
[]string{EventSchema},
) )
go trace.Run() go trace.Run()
tracer := trace.AddProducer() tracer := trace.AddProducer()

View File

@@ -12,6 +12,9 @@ import (
"github.com/quic-go/quic-go/qlogwriter" "github.com/quic-go/quic-go/qlogwriter"
) )
// EventSchema is the qlog event schema for QUIC
const EventSchema = "urn:ietf:params:qlog:events:quic-12"
// DefaultConnectionTracer creates a qlog file in the qlog directory specified by the QLOGDIR environment variable. // DefaultConnectionTracer creates a qlog file in the qlog directory specified by the QLOGDIR environment variable.
// File names are <odcid>_<perspective>.sqlog. // File names are <odcid>_<perspective>.sqlog.
// Returns nil if QLOGDIR is not set. // Returns nil if QLOGDIR is not set.
@@ -35,7 +38,12 @@ func DefaultConnectionTracer(_ context.Context, isClient bool, connID Connection
log.Printf("Failed to create qlog file %s: %s", path, err.Error()) log.Printf("Failed to create qlog file %s: %s", path, err.Error())
return nil return nil
} }
fileSeq := qlogwriter.NewConnectionFileSeq(utils.NewBufferedWriteCloser(bufio.NewWriter(f), f), isClient, connID) fileSeq := qlogwriter.NewConnectionFileSeq(
utils.NewBufferedWriteCloser(bufio.NewWriter(f), f),
isClient,
connID,
[]string{EventSchema},
)
go fileSeq.Run() go fileSeq.Run()
return fileSeq return fileSeq
} }

View File

@@ -42,6 +42,7 @@ type traceHeader struct {
VantagePointType string VantagePointType string
GroupID *ConnectionID GroupID *ConnectionID
ReferenceTime time.Time ReferenceTime time.Time
EventSchemas []string
} }
func (l traceHeader) Encode(enc *jsontext.Encoder) error { func (l traceHeader) Encode(enc *jsontext.Encoder) error {
@@ -59,6 +60,15 @@ func (l traceHeader) Encode(enc *jsontext.Encoder) error {
h.WriteToken(jsontext.String("trace")) h.WriteToken(jsontext.String("trace"))
// trace // trace
h.WriteToken(jsontext.BeginObject) h.WriteToken(jsontext.BeginObject)
if len(l.EventSchemas) > 0 {
h.WriteToken(jsontext.String("event_schemas"))
h.WriteToken(jsontext.BeginArray)
for _, schema := range l.EventSchemas {
h.WriteToken(jsontext.String(schema))
}
h.WriteToken(jsontext.EndArray)
}
h.WriteToken(jsontext.String("vantage_point")) h.WriteToken(jsontext.String("vantage_point"))
// -- vantage_point // -- vantage_point
h.WriteToken(jsontext.BeginObject) h.WriteToken(jsontext.BeginObject)

View File

@@ -48,7 +48,7 @@ func TestTraceMetadata(t *testing.T) {
producer := trace.AddProducer() producer := trace.AddProducer()
producer.Close() producer.Close()
testTraceMetadata(t, buf, "transport", "") testTraceMetadata(t, buf, "transport", "", []string{})
}) })
t.Run("connection trace", func(t *testing.T) { t.Run("connection trace", func(t *testing.T) {
@@ -57,16 +57,27 @@ func TestTraceMetadata(t *testing.T) {
nopWriteCloser(buf), nopWriteCloser(buf),
false, false,
protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}), protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
[]string{"urn:ietf:params:qlog:events:foo", "urn:ietf:params:qlog:events:bar"},
) )
go trace.Run() go trace.Run()
producer := trace.AddProducer() producer := trace.AddProducer()
producer.Close() producer.Close()
testTraceMetadata(t, buf, "server", "deadbeef") testTraceMetadata(t,
buf,
"server",
"deadbeef",
[]string{"urn:ietf:params:qlog:events:foo", "urn:ietf:params:qlog:events:bar"},
)
}) })
} }
func testTraceMetadata(t *testing.T, buf *bytes.Buffer, expectedVantagePoint, expectedGroupID string) { func testTraceMetadata(t *testing.T,
buf *bytes.Buffer,
expectedVantagePoint,
expectedGroupID string,
expectedEventSchemas []string,
) {
var m map[string]any var m map[string]any
require.NoError(t, unmarshal(buf.Bytes(), &m)) require.NoError(t, unmarshal(buf.Bytes(), &m))
require.Equal(t, "0.3", m["qlog_version"]) require.Equal(t, "0.3", m["qlog_version"])
@@ -95,4 +106,13 @@ func testTraceMetadata(t *testing.T, buf *bytes.Buffer, expectedVantagePoint, ex
require.Contains(t, tr, "vantage_point") require.Contains(t, tr, "vantage_point")
vantagePoint := tr["vantage_point"].(map[string]any) vantagePoint := tr["vantage_point"].(map[string]any)
require.Equal(t, expectedVantagePoint, vantagePoint["type"]) require.Equal(t, expectedVantagePoint, vantagePoint["type"])
if len(expectedEventSchemas) > 0 {
require.Contains(t, tr, "event_schemas")
eventSchemas := tr["event_schemas"].([]any)
for i, schema := range eventSchemas {
require.Equal(t, expectedEventSchemas[i], schema)
}
} else {
require.NotContains(t, tr, "event_schemas")
}
} }

View File

@@ -56,19 +56,19 @@ var _ Trace = &FileSeq{}
// NewFileSeq creates a new JSON-SEQ qlog trace to log transport events. // NewFileSeq creates a new JSON-SEQ qlog trace to log transport events.
func NewFileSeq(w io.WriteCloser) *FileSeq { func NewFileSeq(w io.WriteCloser) *FileSeq {
return newFileSeq(w, "transport", nil) return newFileSeq(w, "transport", nil, nil)
} }
// NewConnectionFileSeq creates a new qlog trace to log connection events. // NewConnectionFileSeq creates a new qlog trace to log connection events.
func NewConnectionFileSeq(w io.WriteCloser, isClient bool, odcid ConnectionID) *FileSeq { func NewConnectionFileSeq(w io.WriteCloser, isClient bool, odcid ConnectionID, eventSchemas []string) *FileSeq {
pers := "server" pers := "server"
if isClient { if isClient {
pers = "client" pers = "client"
} }
return newFileSeq(w, pers, &odcid) return newFileSeq(w, pers, &odcid, eventSchemas)
} }
func newFileSeq(w io.WriteCloser, pers string, odcid *ConnectionID) *FileSeq { func newFileSeq(w io.WriteCloser, pers string, odcid *ConnectionID, eventSchemas []string) *FileSeq {
now := time.Now() now := time.Now()
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
enc := jsontext.NewEncoder(buf) enc := jsontext.NewEncoder(buf)
@@ -79,6 +79,7 @@ func newFileSeq(w io.WriteCloser, pers string, odcid *ConnectionID) *FileSeq {
VantagePointType: pers, VantagePointType: pers,
GroupID: odcid, GroupID: odcid,
ReferenceTime: now, ReferenceTime: now,
EventSchemas: eventSchemas,
}).Encode(enc); err != nil { }).Encode(enc); err != nil {
panic(fmt.Sprintf("qlog encoding into a bytes.Buffer failed: %s", err)) panic(fmt.Sprintf("qlog encoding into a bytes.Buffer failed: %s", err))
} }