|
| 1 | +// Copyright 2023 The Go Authors. All rights reserved. |
| 2 | +// Use of this source code is governed by a BSD-style |
| 3 | +// license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +//go:build go1.21 |
| 6 | + |
| 7 | +package quic |
| 8 | + |
| 9 | +import ( |
| 10 | + "context" |
| 11 | + "encoding/hex" |
| 12 | + "log/slog" |
| 13 | + "net/netip" |
| 14 | +) |
| 15 | + |
| 16 | +// Log levels for qlog events. |
| 17 | +const ( |
| 18 | + // QLogLevelFrame includes per-frame information. |
| 19 | + // When this level is enabled, packet_sent and packet_received events will |
| 20 | + // contain information on individual frames sent/received. |
| 21 | + QLogLevelFrame = slog.Level(-6) |
| 22 | + |
| 23 | + // QLogLevelPacket events occur at most once per packet sent or received. |
| 24 | + // |
| 25 | + // For example: packet_sent, packet_received. |
| 26 | + QLogLevelPacket = slog.Level(-4) |
| 27 | + |
| 28 | + // QLogLevelConn events occur multiple times over a connection's lifetime, |
| 29 | + // but less often than the frequency of individual packets. |
| 30 | + // |
| 31 | + // For example: connection_state_updated. |
| 32 | + QLogLevelConn = slog.Level(-2) |
| 33 | + |
| 34 | + // QLogLevelEndpoint events occur at most once per connection. |
| 35 | + // |
| 36 | + // For example: connection_started, connection_closed. |
| 37 | + QLogLevelEndpoint = slog.Level(0) |
| 38 | +) |
| 39 | + |
| 40 | +func (c *Conn) logEnabled(level slog.Level) bool { |
| 41 | + return c.log != nil && c.log.Enabled(context.Background(), level) |
| 42 | +} |
| 43 | + |
| 44 | +// slogHexstring returns a slog.Attr for a value of the hexstring type. |
| 45 | +// |
| 46 | +// https://www.ietf.org/archive/id/draft-ietf-quic-qlog-main-schema-04.html#section-1.1.1 |
| 47 | +func slogHexstring(key string, value []byte) slog.Attr { |
| 48 | + return slog.String(key, hex.EncodeToString(value)) |
| 49 | +} |
| 50 | + |
| 51 | +func slogAddr(key string, value netip.Addr) slog.Attr { |
| 52 | + return slog.String(key, value.String()) |
| 53 | +} |
| 54 | + |
| 55 | +func (c *Conn) logConnectionStarted(originalDstConnID []byte, peerAddr netip.AddrPort) { |
| 56 | + if c.config.QLogLogger == nil || |
| 57 | + !c.config.QLogLogger.Enabled(context.Background(), QLogLevelEndpoint) { |
| 58 | + return |
| 59 | + } |
| 60 | + var vantage string |
| 61 | + if c.side == clientSide { |
| 62 | + vantage = "client" |
| 63 | + originalDstConnID = c.connIDState.originalDstConnID |
| 64 | + } else { |
| 65 | + vantage = "server" |
| 66 | + } |
| 67 | + // A qlog Trace container includes some metadata (title, description, vantage_point) |
| 68 | + // and a list of Events. The Trace also includes a common_fields field setting field |
| 69 | + // values common to all events in the trace. |
| 70 | + // |
| 71 | + // Trace = { |
| 72 | + // ? title: text |
| 73 | + // ? description: text |
| 74 | + // ? configuration: Configuration |
| 75 | + // ? common_fields: CommonFields |
| 76 | + // ? vantage_point: VantagePoint |
| 77 | + // events: [* Event] |
| 78 | + // } |
| 79 | + // |
| 80 | + // To map this into slog's data model, we start each per-connection trace with a With |
| 81 | + // call that includes both the trace metadata and the common fields. |
| 82 | + // |
| 83 | + // This means that in slog's model, each trace event will also include |
| 84 | + // the Trace metadata fields (vantage_point), which is a divergence from the qlog model. |
| 85 | + c.log = c.config.QLogLogger.With( |
| 86 | + // The group_id permits associating traces taken from different vantage points |
| 87 | + // for the same connection. |
| 88 | + // |
| 89 | + // We use the original destination connection ID as the group ID. |
| 90 | + // |
| 91 | + // https://www.ietf.org/archive/id/draft-ietf-quic-qlog-main-schema-04.html#section-3.4.6 |
| 92 | + slogHexstring("group_id", originalDstConnID), |
| 93 | + slog.Group("vantage_point", |
| 94 | + slog.String("name", "go quic"), |
| 95 | + slog.String("type", vantage), |
| 96 | + ), |
| 97 | + ) |
| 98 | + localAddr := c.listener.LocalAddr() |
| 99 | + // https://www.ietf.org/archive/id/draft-ietf-quic-qlog-quic-events-03.html#section-4.2 |
| 100 | + c.log.LogAttrs(context.Background(), QLogLevelEndpoint, |
| 101 | + "connectivity:connection_started", |
| 102 | + slogAddr("src_ip", localAddr.Addr()), |
| 103 | + slog.Int("src_port", int(localAddr.Port())), |
| 104 | + slogHexstring("src_cid", c.connIDState.local[0].cid), |
| 105 | + slogAddr("dst_ip", peerAddr.Addr()), |
| 106 | + slog.Int("dst_port", int(peerAddr.Port())), |
| 107 | + slogHexstring("dst_cid", c.connIDState.remote[0].cid), |
| 108 | + ) |
| 109 | +} |
| 110 | + |
| 111 | +func (c *Conn) logConnectionClosed() { |
| 112 | + if !c.logEnabled(QLogLevelEndpoint) { |
| 113 | + return |
| 114 | + } |
| 115 | + err := c.lifetime.finalErr |
| 116 | + trigger := "error" |
| 117 | + switch e := err.(type) { |
| 118 | + case *ApplicationError: |
| 119 | + // TODO: Distinguish between peer and locally-initiated close. |
| 120 | + trigger = "application" |
| 121 | + case localTransportError: |
| 122 | + if e.code == errNo { |
| 123 | + trigger = "clean" |
| 124 | + } |
| 125 | + case peerTransportError: |
| 126 | + if e.code == errNo { |
| 127 | + trigger = "clean" |
| 128 | + } |
| 129 | + default: |
| 130 | + switch err { |
| 131 | + case errStatelessReset: |
| 132 | + trigger = "stateless_reset" |
| 133 | + } |
| 134 | + // TODO: idle_timeout, handshake_timeout |
| 135 | + } |
| 136 | + // https://www.ietf.org/archive/id/draft-ietf-quic-qlog-quic-events-03.html#section-4.3 |
| 137 | + c.log.LogAttrs(context.Background(), QLogLevelEndpoint, |
| 138 | + "connectivity:connection_closed", |
| 139 | + slog.String("trigger", trigger), |
| 140 | + ) |
| 141 | +} |
0 commit comments