forked from quic-go/quic-go
http3: add the qlog event schema to trace header (#5383)
This commit is contained in:
@@ -13,8 +13,8 @@ import (
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"github.com/quic-go/quic-go/http3/qlog"
|
||||
"github.com/quic-go/quic-go/internal/testdata"
|
||||
"github.com/quic-go/quic-go/qlog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"github.com/quic-go/quic-go/http3/qlog"
|
||||
"github.com/quic-go/quic-go/internal/testdata"
|
||||
"github.com/quic-go/quic-go/qlog"
|
||||
)
|
||||
|
||||
type binds []string
|
||||
|
||||
@@ -66,7 +66,7 @@ func newConnection(
|
||||
idleTimeout time.Duration,
|
||||
) *Conn {
|
||||
var qlogger qlogwriter.Recorder
|
||||
if qlogTrace := quicConn.QlogTrace(); qlogTrace != nil {
|
||||
if qlogTrace := quicConn.QlogTrace(); qlogTrace != nil && qlogTrace.SupportsSchemas(qlog.EventSchema) {
|
||||
qlogger = qlogTrace.AddProducer()
|
||||
}
|
||||
c := &Conn{
|
||||
|
||||
@@ -143,6 +143,8 @@ type qlogTrace struct {
|
||||
recorder qlogwriter.Recorder
|
||||
}
|
||||
|
||||
func (t *qlogTrace) SupportsSchemas(schema string) bool { return true }
|
||||
|
||||
func (t *qlogTrace) AddProducer() qlogwriter.Recorder {
|
||||
return t.recorder
|
||||
}
|
||||
|
||||
15
http3/qlog/qlog_dir.go
Normal file
15
http3/qlog/qlog_dir.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package qlog
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/qlog"
|
||||
"github.com/quic-go/quic-go/qlogwriter"
|
||||
)
|
||||
|
||||
const EventSchema = "urn:ietf:params:qlog:events:http3-12"
|
||||
|
||||
func DefaultConnectionTracer(ctx context.Context, isClient bool, connID quic.ConnectionID) qlogwriter.Trace {
|
||||
return qlog.DefaultConnectionTracerWithSchemas(ctx, isClient, connID, []string{qlog.EventSchema, EventSchema})
|
||||
}
|
||||
41
http3/qlog/qlog_dir_test.go
Normal file
41
http3/qlog/qlog_dir_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package qlog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/qlog"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestQLOGDIRSet(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
connID := quic.ConnectionIDFromBytes([]byte{1, 2, 3, 4})
|
||||
qlogDir := filepath.Join(tmpDir, "qlogs")
|
||||
t.Setenv("QLOGDIR", qlogDir)
|
||||
|
||||
tracer := DefaultConnectionTracer(context.Background(), true, connID)
|
||||
require.NotNil(t, tracer)
|
||||
|
||||
// adding and closing a producer makes the tracer close the file
|
||||
recorder := tracer.AddProducer()
|
||||
recorder.Close()
|
||||
|
||||
_, err := os.Stat(qlogDir)
|
||||
qlogDirCreated := !os.IsNotExist(err)
|
||||
require.True(t, qlogDirCreated)
|
||||
|
||||
entries, err := os.ReadDir(qlogDir)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, entries, 1)
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(qlogDir, entries[0].Name()))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Contains(t, string(data), EventSchema)
|
||||
require.Contains(t, string(data), qlog.EventSchema)
|
||||
}
|
||||
@@ -441,7 +441,7 @@ func (s *Server) removeListener(l *QUICListener) {
|
||||
// It blocks until all HTTP handlers for all streams have returned.
|
||||
func (s *Server) handleConn(conn *quic.Conn) error {
|
||||
var qlogger qlogwriter.Recorder
|
||||
if qlogTrace := conn.QlogTrace(); qlogTrace != nil {
|
||||
if qlogTrace := conn.QlogTrace(); qlogTrace != nil && qlogTrace.SupportsSchemas(qlog.EventSchema) {
|
||||
qlogger = qlogTrace.AddProducer()
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +137,10 @@ func (t *multiplexedTrace) AddProducer() qlogwriter.Recorder {
|
||||
return &multiplexedRecorder{Recorders: recorders}
|
||||
}
|
||||
|
||||
func (t *multiplexedTrace) SupportsSchemas(schema string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func getQuicConfig(conf *quic.Config) *quic.Config {
|
||||
if conf == nil {
|
||||
conf = &quic.Config{}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
h3qlog "github.com/quic-go/quic-go/http3/qlog"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
"github.com/quic-go/quic-go/qlog"
|
||||
"github.com/quic-go/quic-go/qlogwriter"
|
||||
@@ -46,7 +47,7 @@ func NewQlogConnectionTracer(logger io.Writer) func(ctx context.Context, isClien
|
||||
utils.NewBufferedWriteCloser(bufio.NewWriter(f), f),
|
||||
isClient,
|
||||
connID,
|
||||
[]string{qlog.EventSchema},
|
||||
[]string{qlog.EventSchema, h3qlog.EventSchema},
|
||||
)
|
||||
go fileSeq.Run()
|
||||
return fileSeq
|
||||
|
||||
@@ -74,6 +74,8 @@ type multiplexedTrace struct {
|
||||
|
||||
var _ qlogwriter.Trace = &multiplexedTrace{}
|
||||
|
||||
func (t *multiplexedTrace) SupportsSchemas(schema string) bool { return true }
|
||||
|
||||
func (t *multiplexedTrace) AddProducer() qlogwriter.Recorder {
|
||||
recorders := make([]qlogwriter.Recorder, 0, len(t.Traces))
|
||||
for _, tr := range t.Traces {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
h3qlog "github.com/quic-go/quic-go/http3/qlog"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
"github.com/quic-go/quic-go/qlog"
|
||||
"github.com/quic-go/quic-go/qlogwriter"
|
||||
@@ -50,7 +51,7 @@ func NewQLOGConnectionTracer(_ context.Context, isClient bool, connID quic.Conne
|
||||
utils.NewBufferedWriteCloser(bufio.NewWriter(f), f),
|
||||
isClient,
|
||||
connID,
|
||||
[]string{qlog.EventSchema},
|
||||
[]string{qlog.EventSchema, h3qlog.EventSchema},
|
||||
)
|
||||
go fileSeq.Run()
|
||||
return fileSeq
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
@@ -19,6 +20,17 @@ const EventSchema = "urn:ietf:params:qlog:events:quic-12"
|
||||
// File names are <odcid>_<perspective>.sqlog.
|
||||
// Returns nil if QLOGDIR is not set.
|
||||
func DefaultConnectionTracer(_ context.Context, isClient bool, connID ConnectionID) qlogwriter.Trace {
|
||||
return defaultConnectionTracerWithSchemas(isClient, connID, []string{EventSchema})
|
||||
}
|
||||
|
||||
func DefaultConnectionTracerWithSchemas(_ context.Context, isClient bool, connID ConnectionID, eventSchemas []string) qlogwriter.Trace {
|
||||
if !slices.Contains(eventSchemas, EventSchema) {
|
||||
eventSchemas = append([]string{EventSchema}, eventSchemas...)
|
||||
}
|
||||
return defaultConnectionTracerWithSchemas(isClient, connID, eventSchemas)
|
||||
}
|
||||
|
||||
func defaultConnectionTracerWithSchemas(isClient bool, connID ConnectionID, eventSchemas []string) qlogwriter.Trace {
|
||||
qlogDir := os.Getenv("QLOGDIR")
|
||||
if qlogDir == "" {
|
||||
return nil
|
||||
@@ -42,7 +54,7 @@ func DefaultConnectionTracer(_ context.Context, isClient bool, connID Connection
|
||||
utils.NewBufferedWriteCloser(bufio.NewWriter(f), f),
|
||||
isClient,
|
||||
connID,
|
||||
[]string{EventSchema},
|
||||
eventSchemas,
|
||||
)
|
||||
go fileSeq.Run()
|
||||
return fileSeq
|
||||
|
||||
@@ -2,11 +2,14 @@ package qlog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/qlogwriter"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -17,10 +20,21 @@ func TestQLOGDIRSet(t *testing.T) {
|
||||
qlogDir := filepath.Join(tmpDir, "qlogs")
|
||||
t.Setenv("QLOGDIR", qlogDir)
|
||||
|
||||
tracer := DefaultConnectionTracer(context.Background(), true, connID)
|
||||
t.Run("default connection tracer", func(t *testing.T) {
|
||||
tracer := DefaultConnectionTracer(context.Background(), true, connID)
|
||||
testQLOGDIRSet(t, qlogDir, tracer, []string{EventSchema})
|
||||
})
|
||||
|
||||
t.Run("default connection tracer with schemas", func(t *testing.T) {
|
||||
tracer := DefaultConnectionTracerWithSchemas(context.Background(), true, connID, []string{"urn:ietf:params:qlog:events:foobar"})
|
||||
testQLOGDIRSet(t, qlogDir, tracer, []string{EventSchema, "urn:ietf:params:qlog:events:foobar"})
|
||||
})
|
||||
}
|
||||
|
||||
func testQLOGDIRSet(t *testing.T, qlogDir string, tracer qlogwriter.Trace, expectedEventSchemas []string) {
|
||||
require.NotNil(t, tracer)
|
||||
|
||||
// adddng and closing a producer makes the tracer close the file
|
||||
// adding and closing a producer makes the tracer close the file
|
||||
recorder := tracer.AddProducer()
|
||||
recorder.Close()
|
||||
|
||||
@@ -31,6 +45,20 @@ func TestQLOGDIRSet(t *testing.T) {
|
||||
entries, err := os.ReadDir(qlogDir)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, entries, 1)
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(qlogDir, entries[0].Name()))
|
||||
require.NoError(t, err)
|
||||
|
||||
var obj map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(strings.Split(string(data), "\n")[0])[1:], &obj))
|
||||
require.Contains(t, obj, "trace")
|
||||
require.IsType(t, obj["trace"], map[string]any{})
|
||||
require.Contains(t, obj["trace"], "event_schemas")
|
||||
var eventSchemas []string
|
||||
for _, v := range obj["trace"].(map[string]any)["event_schemas"].([]any) {
|
||||
eventSchemas = append(eventSchemas, v.(string))
|
||||
}
|
||||
require.Equal(t, eventSchemas, expectedEventSchemas)
|
||||
}
|
||||
|
||||
func TestQLOGDIRNotSet(t *testing.T) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -18,6 +19,9 @@ type Trace interface {
|
||||
// AddProducer creates a new Recorder for this trace.
|
||||
// Each Recorder can record events independently.
|
||||
AddProducer() Recorder
|
||||
|
||||
// SupportsSchemas returns true if the trace supports the given schema.
|
||||
SupportsSchemas(schema string) bool
|
||||
}
|
||||
|
||||
// Recorder is used to record events to a qlog trace.
|
||||
@@ -67,6 +71,8 @@ type FileSeq struct {
|
||||
mx sync.Mutex
|
||||
producers int
|
||||
closed bool
|
||||
|
||||
eventSchemas []string
|
||||
}
|
||||
|
||||
var _ Trace = &FileSeq{}
|
||||
@@ -112,6 +118,10 @@ func newFileSeq(w io.WriteCloser, pers string, odcid *ConnectionID, eventSchemas
|
||||
}
|
||||
}
|
||||
|
||||
func (t *FileSeq) SupportsSchemas(schema string) bool {
|
||||
return slices.Contains(t.eventSchemas, schema)
|
||||
}
|
||||
|
||||
func (t *FileSeq) AddProducer() Recorder {
|
||||
t.mx.Lock()
|
||||
defer t.mx.Unlock()
|
||||
|
||||
@@ -26,6 +26,10 @@ func (t *Trace) AddProducer() qlogwriter.Recorder {
|
||||
return t.Recorder
|
||||
}
|
||||
|
||||
func (t *Trace) SupportsSchemas(string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Recorder is a qlog.Recorder that records events.
|
||||
// Events can be retrieved using the Events method.
|
||||
type Recorder struct {
|
||||
|
||||
Reference in New Issue
Block a user