1// Copyright 2018, OpenCensus Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package ochttp
16
17import (
18	"crypto/tls"
19	"net/http"
20	"net/http/httptrace"
21	"strings"
22
23	"go.opencensus.io/trace"
24)
25
26type spanAnnotator struct {
27	sp *trace.Span
28}
29
30// TODO: Remove NewSpanAnnotator at the next release.
31
32// NewSpanAnnotator returns a httptrace.ClientTrace which annotates
33// all emitted httptrace events on the provided Span.
34// Deprecated: Use NewSpanAnnotatingClientTrace instead
35func NewSpanAnnotator(r *http.Request, s *trace.Span) *httptrace.ClientTrace {
36	return NewSpanAnnotatingClientTrace(r, s)
37}
38
39// NewSpanAnnotatingClientTrace returns a httptrace.ClientTrace which annotates
40// all emitted httptrace events on the provided Span.
41func NewSpanAnnotatingClientTrace(_ *http.Request, s *trace.Span) *httptrace.ClientTrace {
42	sa := spanAnnotator{sp: s}
43
44	return &httptrace.ClientTrace{
45		GetConn:              sa.getConn,
46		GotConn:              sa.gotConn,
47		PutIdleConn:          sa.putIdleConn,
48		GotFirstResponseByte: sa.gotFirstResponseByte,
49		Got100Continue:       sa.got100Continue,
50		DNSStart:             sa.dnsStart,
51		DNSDone:              sa.dnsDone,
52		ConnectStart:         sa.connectStart,
53		ConnectDone:          sa.connectDone,
54		TLSHandshakeStart:    sa.tlsHandshakeStart,
55		TLSHandshakeDone:     sa.tlsHandshakeDone,
56		WroteHeaders:         sa.wroteHeaders,
57		Wait100Continue:      sa.wait100Continue,
58		WroteRequest:         sa.wroteRequest,
59	}
60}
61
62func (s spanAnnotator) getConn(hostPort string) {
63	attrs := []trace.Attribute{
64		trace.StringAttribute("httptrace.get_connection.host_port", hostPort),
65	}
66	s.sp.Annotate(attrs, "GetConn")
67}
68
69func (s spanAnnotator) gotConn(info httptrace.GotConnInfo) {
70	attrs := []trace.Attribute{
71		trace.BoolAttribute("httptrace.got_connection.reused", info.Reused),
72		trace.BoolAttribute("httptrace.got_connection.was_idle", info.WasIdle),
73	}
74	if info.WasIdle {
75		attrs = append(attrs,
76			trace.StringAttribute("httptrace.got_connection.idle_time", info.IdleTime.String()))
77	}
78	s.sp.Annotate(attrs, "GotConn")
79}
80
81// PutIdleConn implements a httptrace.ClientTrace hook
82func (s spanAnnotator) putIdleConn(err error) {
83	var attrs []trace.Attribute
84	if err != nil {
85		attrs = append(attrs,
86			trace.StringAttribute("httptrace.put_idle_connection.error", err.Error()))
87	}
88	s.sp.Annotate(attrs, "PutIdleConn")
89}
90
91func (s spanAnnotator) gotFirstResponseByte() {
92	s.sp.Annotate(nil, "GotFirstResponseByte")
93}
94
95func (s spanAnnotator) got100Continue() {
96	s.sp.Annotate(nil, "Got100Continue")
97}
98
99func (s spanAnnotator) dnsStart(info httptrace.DNSStartInfo) {
100	attrs := []trace.Attribute{
101		trace.StringAttribute("httptrace.dns_start.host", info.Host),
102	}
103	s.sp.Annotate(attrs, "DNSStart")
104}
105
106func (s spanAnnotator) dnsDone(info httptrace.DNSDoneInfo) {
107	var addrs []string
108	for _, addr := range info.Addrs {
109		addrs = append(addrs, addr.String())
110	}
111	attrs := []trace.Attribute{
112		trace.StringAttribute("httptrace.dns_done.addrs", strings.Join(addrs, " , ")),
113	}
114	if info.Err != nil {
115		attrs = append(attrs,
116			trace.StringAttribute("httptrace.dns_done.error", info.Err.Error()))
117	}
118	s.sp.Annotate(attrs, "DNSDone")
119}
120
121func (s spanAnnotator) connectStart(network, addr string) {
122	attrs := []trace.Attribute{
123		trace.StringAttribute("httptrace.connect_start.network", network),
124		trace.StringAttribute("httptrace.connect_start.addr", addr),
125	}
126	s.sp.Annotate(attrs, "ConnectStart")
127}
128
129func (s spanAnnotator) connectDone(network, addr string, err error) {
130	attrs := []trace.Attribute{
131		trace.StringAttribute("httptrace.connect_done.network", network),
132		trace.StringAttribute("httptrace.connect_done.addr", addr),
133	}
134	if err != nil {
135		attrs = append(attrs,
136			trace.StringAttribute("httptrace.connect_done.error", err.Error()))
137	}
138	s.sp.Annotate(attrs, "ConnectDone")
139}
140
141func (s spanAnnotator) tlsHandshakeStart() {
142	s.sp.Annotate(nil, "TLSHandshakeStart")
143}
144
145func (s spanAnnotator) tlsHandshakeDone(_ tls.ConnectionState, err error) {
146	var attrs []trace.Attribute
147	if err != nil {
148		attrs = append(attrs,
149			trace.StringAttribute("httptrace.tls_handshake_done.error", err.Error()))
150	}
151	s.sp.Annotate(attrs, "TLSHandshakeDone")
152}
153
154func (s spanAnnotator) wroteHeaders() {
155	s.sp.Annotate(nil, "WroteHeaders")
156}
157
158func (s spanAnnotator) wait100Continue() {
159	s.sp.Annotate(nil, "Wait100Continue")
160}
161
162func (s spanAnnotator) wroteRequest(info httptrace.WroteRequestInfo) {
163	var attrs []trace.Attribute
164	if info.Err != nil {
165		attrs = append(attrs,
166			trace.StringAttribute("httptrace.wrote_request.error", info.Err.Error()))
167	}
168	s.sp.Annotate(attrs, "WroteRequest")
169}
170