github.com/gogf/gf/v2@v2.7.4/net/gclient/gclient_tracer_tracing.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 7 package gclient 8 9 import ( 10 "context" 11 "crypto/tls" 12 "fmt" 13 "io" 14 "net/http" 15 "net/http/httptrace" 16 "net/textproto" 17 "strings" 18 "sync" 19 20 "go.opentelemetry.io/otel/attribute" 21 "go.opentelemetry.io/otel/codes" 22 "go.opentelemetry.io/otel/trace" 23 24 "github.com/gogf/gf/v2/internal/utils" 25 "github.com/gogf/gf/v2/net/gtrace" 26 "github.com/gogf/gf/v2/util/gconv" 27 ) 28 29 // clientTracerTracing is used for implementing httptrace.ClientTrace. 30 type clientTracerTracing struct { 31 context.Context 32 span trace.Span 33 request *http.Request 34 requestBody []byte 35 headers map[string]interface{} 36 mtx sync.Mutex 37 } 38 39 // newClientTracerTracing creates and returns object of httptrace.ClientTrace. 40 func newClientTracerTracing( 41 ctx context.Context, 42 span trace.Span, 43 request *http.Request, 44 ) *httptrace.ClientTrace { 45 ct := &clientTracerTracing{ 46 Context: ctx, 47 span: span, 48 request: request, 49 headers: make(map[string]interface{}), 50 } 51 52 reqBodyContent, _ := io.ReadAll(ct.request.Body) 53 ct.requestBody = reqBodyContent 54 ct.request.Body = utils.NewReadCloser(reqBodyContent, false) 55 56 return &httptrace.ClientTrace{ 57 GetConn: ct.GetConn, 58 GotConn: ct.GotConn, 59 PutIdleConn: ct.PutIdleConn, 60 GotFirstResponseByte: ct.GotFirstResponseByte, 61 Got100Continue: ct.Got100Continue, 62 Got1xxResponse: ct.Got1xxResponse, 63 DNSStart: ct.DNSStart, 64 DNSDone: ct.DNSDone, 65 ConnectStart: ct.ConnectStart, 66 ConnectDone: ct.ConnectDone, 67 TLSHandshakeStart: ct.TLSHandshakeStart, 68 TLSHandshakeDone: ct.TLSHandshakeDone, 69 WroteHeaderField: ct.WroteHeaderField, 70 WroteHeaders: ct.WroteHeaders, 71 Wait100Continue: ct.Wait100Continue, 72 WroteRequest: ct.WroteRequest, 73 } 74 } 75 76 // GetConn is called before a connection is created or 77 // retrieved from an idle pool. The hostPort is the 78 // "host:port" of the target or proxy. GetConn is called even 79 // if there's already an idle cached connection available. 80 func (ct *clientTracerTracing) GetConn(host string) {} 81 82 // GotConn is called after a successful connection is 83 // obtained. There is no hook for failure to obtain a 84 // connection; instead, use the error from 85 // Transport.RoundTrip. 86 func (ct *clientTracerTracing) GotConn(info httptrace.GotConnInfo) { 87 remoteAddr := "" 88 if info.Conn.RemoteAddr() != nil { 89 remoteAddr = info.Conn.RemoteAddr().String() 90 } 91 localAddr := "" 92 if info.Conn.LocalAddr() != nil { 93 localAddr = info.Conn.LocalAddr().String() 94 } 95 ct.span.SetAttributes( 96 attribute.String(tracingAttrHttpAddressRemote, remoteAddr), 97 attribute.String(tracingAttrHttpAddressLocal, localAddr), 98 ) 99 } 100 101 // PutIdleConn is called when the connection is returned to 102 // the idle pool. If err is nil, the connection was 103 // successfully returned to the idle pool. If err is non-nil, 104 // it describes why not. PutIdleConn is not called if 105 // connection reuse is disabled via Transport.DisableKeepAlives. 106 // PutIdleConn is called before the caller's Response.Body.Close 107 // call returns. 108 // For HTTP/2, this hook is not currently used. 109 func (ct *clientTracerTracing) PutIdleConn(err error) { 110 if err != nil { 111 ct.span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, err)) 112 } 113 } 114 115 // GotFirstResponseByte is called when the first byte of the response 116 // headers is available. 117 func (ct *clientTracerTracing) GotFirstResponseByte() {} 118 119 // Got100Continue is called if the server replies with a "100 120 // Continue" response. 121 func (ct *clientTracerTracing) Got100Continue() {} 122 123 // Got1xxResponse is called for each 1xx informational response header 124 // returned before the final non-1xx response. Got1xxResponse is called 125 // for "100 Continue" responses, even if Got100Continue is also defined. 126 // If it returns an error, the client request is aborted with that error value. 127 func (ct *clientTracerTracing) Got1xxResponse(code int, header textproto.MIMEHeader) error { 128 return nil 129 } 130 131 // DNSStart is called when a DNS lookup begins. 132 func (ct *clientTracerTracing) DNSStart(info httptrace.DNSStartInfo) { 133 ct.span.SetAttributes( 134 attribute.String(tracingAttrHttpDnsStart, info.Host), 135 ) 136 } 137 138 // DNSDone is called when a DNS lookup ends. 139 func (ct *clientTracerTracing) DNSDone(info httptrace.DNSDoneInfo) { 140 var buffer strings.Builder 141 for _, v := range info.Addrs { 142 if buffer.Len() != 0 { 143 buffer.WriteString(",") 144 } 145 buffer.WriteString(v.String()) 146 } 147 if info.Err != nil { 148 ct.span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, info.Err)) 149 } 150 ct.span.SetAttributes( 151 attribute.String(tracingAttrHttpDnsDone, buffer.String()), 152 ) 153 } 154 155 // ConnectStart is called when a new connection's Dial begins. 156 // If net.Dialer.DualStack (IPv6 "Happy Eyeballs") support is 157 // enabled, this may be called multiple times. 158 func (ct *clientTracerTracing) ConnectStart(network, addr string) { 159 ct.span.SetAttributes( 160 attribute.String(tracingAttrHttpConnectStart, network+"@"+addr), 161 ) 162 } 163 164 // ConnectDone is called when a new connection's Dial 165 // completes. The provided err indicates whether the 166 // connection completed successfully. 167 // If net.Dialer.DualStack ("Happy Eyeballs") support is 168 // enabled, this may be called multiple times. 169 func (ct *clientTracerTracing) ConnectDone(network, addr string, err error) { 170 if err != nil { 171 ct.span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, err)) 172 } 173 ct.span.SetAttributes( 174 attribute.String(tracingAttrHttpConnectDone, network+"@"+addr), 175 ) 176 } 177 178 // TLSHandshakeStart is called when the TLS handshake is started. When 179 // connecting to an HTTPS site via an HTTP proxy, the handshake happens 180 // after the CONNECT request is processed by the proxy. 181 func (ct *clientTracerTracing) TLSHandshakeStart() {} 182 183 // TLSHandshakeDone is called after the TLS handshake with either the 184 // successful handshake's connection state, or a non-nil error on handshake 185 // failure. 186 func (ct *clientTracerTracing) TLSHandshakeDone(_ tls.ConnectionState, err error) { 187 if err != nil { 188 ct.span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, err)) 189 } 190 } 191 192 // WroteHeaderField is called after the Transport has written 193 // each request header. At the time of this call the values 194 // might be buffered and not yet written to the network. 195 func (ct *clientTracerTracing) WroteHeaderField(k string, v []string) { 196 if len(v) > 1 { 197 ct.headers[k] = v 198 } else if len(v) == 1 { 199 ct.headers[k] = v[0] 200 } 201 } 202 203 // WroteHeaders is called after the Transport has written 204 // all request headers. 205 func (ct *clientTracerTracing) WroteHeaders() {} 206 207 // Wait100Continue is called if the Request specified 208 // "Expect: 100-continue" and the Transport has written the 209 // request headers but is waiting for "100 Continue" from the 210 // server before writing the request body. 211 func (ct *clientTracerTracing) Wait100Continue() {} 212 213 // WroteRequest is called with the result of writing the 214 // request and any body. It may be called multiple times 215 // in the case of retried requests. 216 func (ct *clientTracerTracing) WroteRequest(info httptrace.WroteRequestInfo) { 217 if info.Err != nil { 218 ct.span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, info.Err)) 219 } 220 221 ct.span.AddEvent(tracingEventHttpRequest, trace.WithAttributes( 222 attribute.String(tracingEventHttpRequestHeaders, gconv.String(ct.headers)), 223 attribute.String(tracingEventHttpRequestBaggage, gtrace.GetBaggageMap(ct.Context).String()), 224 )) 225 }