Skip to content

Commit

Permalink
feat: debug connection reuse (#323)
Browse files Browse the repository at this point in the history
Add a new SENTRYGODEBUG option httptrace to print DSN resolution and TCP
connection information, including whether connections are being reused.

Rename internal debug option dumphttp -> httpdump for symmetry with
httptrace (that derives from the net/http/httptrace package name).
  • Loading branch information
rhcarvalho authored Jan 19, 2021
1 parent 35f2080 commit 278837e
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 13 deletions.
19 changes: 18 additions & 1 deletion client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"os"
"reflect"
"sort"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -212,10 +213,26 @@ func NewClient(options ClientOptions) (*Client, error) {
options.Environment = os.Getenv("SENTRY_ENVIRONMENT")
}

if env := os.Getenv("SENTRYGODEBUG"); env == "dumphttp=1" {
// SENTRYGODEBUG is a comma-separated list of key=value pairs (similar
// to GODEBUG). It is not a supported feature: recognized debug options
// may change any time.
//
// The intended public is SDK developers. It is orthogonal to
// options.Debug, which is also available for SDK users.
dbg := strings.Split(os.Getenv("SENTRYGODEBUG"), ",")
sort.Strings(dbg)
// dbgOpt returns true when the given debug option is enabled, for
// example SENTRYGODEBUG=someopt=1.
dbgOpt := func(opt string) bool {
s := opt + "=1"
return dbg[sort.SearchStrings(dbg, s)%len(dbg)] == s
}
if dbgOpt("httpdump") || dbgOpt("httptrace") {
options.HTTPTransport = &debug.Transport{
RoundTripper: http.DefaultTransport,
Output: os.Stderr,
Dump: dbgOpt("httpdump"),
Trace: dbgOpt("httptrace"),
}
}

Expand Down
58 changes: 46 additions & 12 deletions internal/debug/transport.go
Original file line number Diff line number Diff line change
@@ -1,38 +1,72 @@
package debug

import (
"bytes"
"fmt"
"io"
"net/http"
"net/http/httptrace"
"net/http/httputil"
)

// Transport implements http.RoundTripper and can be used to wrap other HTTP
// transports to dump request and responses for debugging.
// transports for debugging, normally http.DefaultTransport.
type Transport struct {
http.RoundTripper
Output io.Writer
// Dump controls whether to dump HTTP request and responses.
Dump bool
// Trace enables usage of net/http/httptrace.
Trace bool
}

func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
b, err := httputil.DumpRequestOut(req, true)
if err != nil {
return nil, err
var buf bytes.Buffer
if t.Dump {
b, err := httputil.DumpRequestOut(req, true)
if err != nil {
panic(err)
}
_, err = buf.Write(ensureTrailingNewline(b))
if err != nil {
panic(err)
}
}
_, err = t.Output.Write(ensureTrailingNewline(b))
if err != nil {
return nil, err
if t.Trace {
trace := &httptrace.ClientTrace{
DNSDone: func(di httptrace.DNSDoneInfo) {
fmt.Fprintf(&buf, "* DNS %v → %v\n", req.Host, di.Addrs)
},
GotConn: func(ci httptrace.GotConnInfo) {
fmt.Fprintf(&buf, "* Connection local=%v remote=%v", ci.Conn.LocalAddr(), ci.Conn.RemoteAddr())
if ci.Reused {
fmt.Fprint(&buf, " (reused)")
}
if ci.WasIdle {
fmt.Fprintf(&buf, " (idle %v)", ci.IdleTime)
}
fmt.Fprintln(&buf)
},
}
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
}
resp, err := t.RoundTripper.RoundTrip(req)
if err != nil {
return nil, err
}
b, err = httputil.DumpResponse(resp, true)
if err != nil {
return nil, err
if t.Dump {
b, err := httputil.DumpResponse(resp, true)
if err != nil {
panic(err)
}
_, err = buf.Write(ensureTrailingNewline(b))
if err != nil {
panic(err)
}
}
_, err = t.Output.Write(ensureTrailingNewline(b))
_, err = io.Copy(t.Output, &buf)
if err != nil {
return nil, err
panic(err)
}
return resp, nil
}
Expand Down

0 comments on commit 278837e

Please sign in to comment.