2019-06-14 13:09:41 -07:00
|
|
|
// Copyright 2019, OpenTelemetry Authors
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2019-06-14 11:37:05 -07:00
|
|
|
package httptrace
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/tls"
|
|
|
|
"net/http/httptrace"
|
|
|
|
"net/textproto"
|
|
|
|
"strings"
|
|
|
|
|
2019-06-26 22:03:09 -07:00
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
|
2019-06-14 11:37:05 -07:00
|
|
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
|
|
|
"github.com/open-telemetry/opentelemetry-go/api/tag"
|
|
|
|
"github.com/open-telemetry/opentelemetry-go/api/trace"
|
2019-06-26 22:03:09 -07:00
|
|
|
"github.com/open-telemetry/opentelemetry-go/sdk/event"
|
2019-06-14 11:37:05 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
clientLevel struct {
|
|
|
|
trace.Span
|
|
|
|
ident string
|
|
|
|
}
|
|
|
|
|
|
|
|
clientTracer struct {
|
|
|
|
context.Context
|
|
|
|
httptrace.ClientTrace
|
|
|
|
|
|
|
|
levels []clientLevel
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
HTTPStatus = tag.New("http.status")
|
|
|
|
HTTPHeaderMIME = tag.New("http.mime")
|
|
|
|
HTTPRemoteAddr = tag.New("http.remote")
|
|
|
|
HTTPLocalAddr = tag.New("http.local")
|
2019-06-26 22:03:09 -07:00
|
|
|
MessageKey = tag.New("message",
|
|
|
|
tag.WithDescription("message text: info, error, etc"),
|
|
|
|
)
|
2019-06-14 11:37:05 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
func newClientTracer(ctx context.Context) *clientTracer {
|
|
|
|
ct := &clientTracer{
|
|
|
|
Context: ctx,
|
|
|
|
}
|
|
|
|
ct.open("http.request")
|
|
|
|
return ct
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) open(name string, attrs ...core.KeyValue) {
|
|
|
|
_, sp := trace.Start(ct.Context, name, trace.WithAttributes(attrs...))
|
|
|
|
ct.levels = append(ct.levels, clientLevel{
|
|
|
|
Span: sp,
|
|
|
|
ident: name,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) close(name string) {
|
|
|
|
if len(ct.levels) == 0 {
|
|
|
|
panic("remove me")
|
|
|
|
}
|
|
|
|
l := len(ct.levels)
|
|
|
|
ct.levels[l-1].Finish()
|
|
|
|
ct.levels = ct.levels[0 : l-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) current() trace.Span {
|
|
|
|
return ct.levels[len(ct.levels)-1].Span
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) currentName() string {
|
|
|
|
if len(ct.levels) == 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return ct.levels[len(ct.levels)-1].ident
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) getConn(host string) {
|
|
|
|
ct.open("http.getconn", HostKey.String(host))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) gotConn(info httptrace.GotConnInfo) {
|
|
|
|
ct.current().SetAttribute(HTTPRemoteAddr.String(info.Conn.RemoteAddr().String()))
|
|
|
|
ct.current().SetAttribute(HTTPLocalAddr.String(info.Conn.LocalAddr().String()))
|
|
|
|
|
|
|
|
ct.close("http.getconn")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) putIdleConn(err error) {
|
|
|
|
if err != nil {
|
2019-06-26 22:03:09 -07:00
|
|
|
ct.current().SetAttribute(MessageKey.String(err.Error()))
|
|
|
|
ct.current().SetStatus(codes.Unknown)
|
2019-06-14 11:37:05 -07:00
|
|
|
}
|
|
|
|
ct.close("http.receive")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) gotFirstResponseByte() {
|
|
|
|
ct.open("http.receive")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) dnsStart(httptrace.DNSStartInfo) {
|
|
|
|
ct.open("http.dns")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) dnsDone(httptrace.DNSDoneInfo) {
|
|
|
|
ct.close("http.dns")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) connectStart(network, addr string) {
|
|
|
|
ct.open("http.connect")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) connectDone(network, addr string, err error) {
|
|
|
|
ct.close("http.connect")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) tlsHandshakeStart() {
|
|
|
|
ct.open("http.tls")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) tlsHandshakeDone(tls.ConnectionState, error) {
|
|
|
|
ct.close("http.tls")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) wroteHeaderField(key string, value []string) {
|
|
|
|
if ct.currentName() != "http.headers" {
|
|
|
|
ct.open("http.headers")
|
|
|
|
}
|
|
|
|
ct.levels[0].SetAttribute(tag.New("http." + strings.ToLower(key)).String(sa2s(value)))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) wroteHeaders() {
|
|
|
|
ct.open("http.send")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) wroteRequest(info httptrace.WroteRequestInfo) {
|
|
|
|
if info.Err != nil {
|
2019-06-26 22:03:09 -07:00
|
|
|
ct.levels[0].SetAttribute(MessageKey.String(info.Err.Error()))
|
|
|
|
ct.levels[0].SetStatus(codes.Unknown)
|
2019-06-14 11:37:05 -07:00
|
|
|
}
|
|
|
|
ct.close("http.send")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) got100Continue() {
|
2019-06-25 13:50:46 -07:00
|
|
|
ct.current().AddEvent(ct.Context, event.WithString("GOT 100 - Continue"))
|
2019-06-14 11:37:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) wait100Continue() {
|
2019-06-25 13:50:46 -07:00
|
|
|
ct.current().AddEvent(ct.Context, event.WithString("GOT 100 - Wait"))
|
2019-06-14 11:37:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ct *clientTracer) got1xxResponse(code int, header textproto.MIMEHeader) error {
|
2019-06-25 13:50:46 -07:00
|
|
|
ct.current().AddEvent(ct.Context, event.WithAttr("GOT 1xx",
|
2019-06-24 10:35:16 -07:00
|
|
|
HTTPStatus.Int(code),
|
|
|
|
HTTPHeaderMIME.String(sm2s(header)),
|
2019-06-25 13:50:46 -07:00
|
|
|
))
|
2019-06-14 11:37:05 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func sa2s(value []string) string {
|
|
|
|
if len(value) == 1 {
|
|
|
|
return value[0]
|
|
|
|
} else if len(value) == 0 {
|
|
|
|
return "undefined"
|
|
|
|
}
|
|
|
|
return strings.Join(value, ",")
|
|
|
|
}
|
|
|
|
|
|
|
|
func sm2s(value map[string][]string) string {
|
|
|
|
var buf strings.Builder
|
|
|
|
for k, v := range value {
|
|
|
|
if buf.Len() != 0 {
|
|
|
|
buf.WriteString(",")
|
|
|
|
}
|
|
|
|
buf.WriteString(k)
|
|
|
|
buf.WriteString("=")
|
|
|
|
buf.WriteString(sa2s(v))
|
|
|
|
}
|
|
|
|
return buf.String()
|
|
|
|
}
|