blob: ea53cab1e816e24494c5d221639e99a0b7df5d2b [file] [log] [blame]
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.21
package quic
import (
"context"
"encoding/hex"
"log/slog"
"net/netip"
)
// Log levels for qlog events.
const (
// QLogLevelFrame includes per-frame information.
// When this level is enabled, packet_sent and packet_received events will
// contain information on individual frames sent/received.
QLogLevelFrame = slog.Level(-6)
// QLogLevelPacket events occur at most once per packet sent or received.
//
// For example: packet_sent, packet_received.
QLogLevelPacket = slog.Level(-4)
// QLogLevelConn events occur multiple times over a connection's lifetime,
// but less often than the frequency of individual packets.
//
// For example: connection_state_updated.
QLogLevelConn = slog.Level(-2)
// QLogLevelEndpoint events occur at most once per connection.
//
// For example: connection_started, connection_closed.
QLogLevelEndpoint = slog.Level(0)
)
func (c *Conn) logEnabled(level slog.Level) bool {
return c.log != nil && c.log.Enabled(context.Background(), level)
}
// slogHexstring returns a slog.Attr for a value of the hexstring type.
//
// https://www.ietf.org/archive/id/draft-ietf-quic-qlog-main-schema-04.html#section-1.1.1
func slogHexstring(key string, value []byte) slog.Attr {
return slog.String(key, hex.EncodeToString(value))
}
func slogAddr(key string, value netip.Addr) slog.Attr {
return slog.String(key, value.String())
}
func (c *Conn) logConnectionStarted(originalDstConnID []byte, peerAddr netip.AddrPort) {
if c.config.QLogLogger == nil ||
!c.config.QLogLogger.Enabled(context.Background(), QLogLevelEndpoint) {
return
}
var vantage string
if c.side == clientSide {
vantage = "client"
originalDstConnID = c.connIDState.originalDstConnID
} else {
vantage = "server"
}
// A qlog Trace container includes some metadata (title, description, vantage_point)
// and a list of Events. The Trace also includes a common_fields field setting field
// values common to all events in the trace.
//
// Trace = {
// ? title: text
// ? description: text
// ? configuration: Configuration
// ? common_fields: CommonFields
// ? vantage_point: VantagePoint
// events: [* Event]
// }
//
// To map this into slog's data model, we start each per-connection trace with a With
// call that includes both the trace metadata and the common fields.
//
// This means that in slog's model, each trace event will also include
// the Trace metadata fields (vantage_point), which is a divergence from the qlog model.
c.log = c.config.QLogLogger.With(
// The group_id permits associating traces taken from different vantage points
// for the same connection.
//
// We use the original destination connection ID as the group ID.
//
// https://www.ietf.org/archive/id/draft-ietf-quic-qlog-main-schema-04.html#section-3.4.6
slogHexstring("group_id", originalDstConnID),
slog.Group("vantage_point",
slog.String("name", "go quic"),
slog.String("type", vantage),
),
)
localAddr := c.endpoint.LocalAddr()
// https://www.ietf.org/archive/id/draft-ietf-quic-qlog-quic-events-03.html#section-4.2
c.log.LogAttrs(context.Background(), QLogLevelEndpoint,
"connectivity:connection_started",
slogAddr("src_ip", localAddr.Addr()),
slog.Int("src_port", int(localAddr.Port())),
slogHexstring("src_cid", c.connIDState.local[0].cid),
slogAddr("dst_ip", peerAddr.Addr()),
slog.Int("dst_port", int(peerAddr.Port())),
slogHexstring("dst_cid", c.connIDState.remote[0].cid),
)
}
func (c *Conn) logConnectionClosed() {
if !c.logEnabled(QLogLevelEndpoint) {
return
}
err := c.lifetime.finalErr
trigger := "error"
switch e := err.(type) {
case *ApplicationError:
// TODO: Distinguish between peer and locally-initiated close.
trigger = "application"
case localTransportError:
switch err {
case errHandshakeTimeout:
trigger = "handshake_timeout"
default:
if e.code == errNo {
trigger = "clean"
}
}
case peerTransportError:
if e.code == errNo {
trigger = "clean"
}
default:
switch err {
case errIdleTimeout:
trigger = "idle_timeout"
case errStatelessReset:
trigger = "stateless_reset"
}
}
// https://www.ietf.org/archive/id/draft-ietf-quic-qlog-quic-events-03.html#section-4.3
c.log.LogAttrs(context.Background(), QLogLevelEndpoint,
"connectivity:connection_closed",
slog.String("trigger", trigger),
)
}