feat: add roundtripperwares

This commit is contained in:
Kevin Franklin Kim 2022-01-25 14:26:33 +01:00
parent 0aa5e02fe9
commit fe636f68ad
14 changed files with 575 additions and 6 deletions

View File

@ -10,6 +10,7 @@ require (
github.com/jackc/pgx/v4 v4.14.1
github.com/nats-io/nats.go v1.13.0
github.com/pkg/errors v0.9.1
github.com/tinylib/msgp v1.1.6 // indirect
go.mongodb.org/mongo-driver v1.8.1
go.opentelemetry.io/otel v0.20.0
go.opentelemetry.io/otel/metric v0.20.0

View File

@ -514,6 +514,8 @@ github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAv
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -619,6 +621,8 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
@ -992,6 +996,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=

View File

@ -0,0 +1,25 @@
package main
import (
"github.com/foomo/keel/log"
"github.com/foomo/keel/net/http"
"github.com/foomo/keel/net/http/roundtripware"
)
func client() {
l := log.Logger()
client := http.NewHTTPClient(
http.HTTPClientWithRoundTripware(l,
roundtripware.Logger(),
),
)
var err error
_, err = client.Get("http://localhost:8080") // nolint:noctx
log.Must(l, err, "failed to retrieve response")
_, err = client.Get("http://localhost:8080/404") // nolint:noctx
log.Must(l, err, "failed to retrieve response")
}

View File

@ -0,0 +1,13 @@
package main
import (
"time"
)
func main() {
go server()
go client()
time.Sleep(time.Second * 5)
}

View File

@ -0,0 +1,33 @@
package main
import (
"net/http"
"github.com/foomo/keel"
)
func server() {
svr := keel.NewServer()
// get logger
l := svr.Logger()
// create demo service
svs := http.NewServeMux()
svs.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
})
svs.HandleFunc("/404", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte("not found"))
})
svr.AddService(
keel.NewServiceHTTP(l, "demo", ":8080", svs),
)
svr.Run()
}

1
go.mod
View File

@ -12,6 +12,7 @@ require (
github.com/prometheus/client_golang v1.11.0
github.com/spf13/viper v1.10.1
github.com/stretchr/testify v1.7.0
github.com/tinylib/msgp v1.1.6
go.mongodb.org/mongo-driver v1.8.1
go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.20.0
go.opentelemetry.io/contrib/instrumentation/host v0.20.0

5
go.sum
View File

@ -514,6 +514,8 @@ github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAv
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -619,6 +621,8 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
@ -992,6 +996,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=

View File

@ -9,8 +9,6 @@ import (
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
httputils "github.com/foomo/keel/net/http"
)
func With(l *zap.Logger, fields ...zap.Field) *zap.Logger {
@ -50,10 +48,10 @@ func WithHTTPRequest(l *zap.Logger, r *http.Request) *zap.Logger {
if r.Host != "" {
fields = append(fields, FHTTPHost(r.Host))
}
if id := r.Header.Get(httputils.HeaderXRequestID); id != "" {
if id := r.Header.Get("X-Request-ID"); id != "" {
fields = append(fields, FHTTPRequestID(id))
}
if id := r.Header.Get(httputils.HeaderXSessionID); id != "" {
if id := r.Header.Get("X-Session-ID"); id != "" {
fields = append(fields, FHTTPSessionID(id))
}
if r.TLS != nil {
@ -68,13 +66,13 @@ func WithHTTPRequest(l *zap.Logger, r *http.Request) *zap.Logger {
}
var clientIP string
if value := r.Header.Get(httputils.HeaderXForwardedFor); value != "" {
if value := r.Header.Get("X-Forwarded-For"); value != "" {
if i := strings.IndexAny(value, ", "); i > 0 {
clientIP = value[:i]
} else {
clientIP = value
}
} else if value := r.Header.Get(httputils.HeaderXRealIP); value != "" {
} else if value := r.Header.Get("X-Real-IP"); value != "" {
clientIP = value
} else if value, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
clientIP = value
@ -91,3 +89,37 @@ func WithHTTPRequest(l *zap.Logger, r *http.Request) *zap.Logger {
return With(l, fields...)
}
func WithHTTPRequestOut(l *zap.Logger, r *http.Request) *zap.Logger {
fields := []zap.Field{
FHTTPWroteBytes(r.ContentLength),
FHTTPMethod(r.Method),
FHTTPTarget(r.URL.Path),
}
if r.URL.Host != "" {
fields = append(fields, FHTTPHost(r.URL.Host))
}
if id := r.Header.Get("X-Request-ID"); id != "" {
fields = append(fields, FHTTPRequestID(id))
}
if id := r.Header.Get("X-Session-ID"); id != "" {
fields = append(fields, FHTTPSessionID(id))
}
if r.TLS != nil {
fields = append(fields, FHTTPScheme("https"))
} else {
fields = append(fields, FHTTPScheme("http"))
}
if r.ProtoMajor == 1 {
fields = append(fields, FHTTPFlavor(fmt.Sprintf("1.%d", r.ProtoMinor)))
} else if r.ProtoMajor == 2 {
fields = append(fields, FHTTPFlavor("2"))
}
if spanCtx := trace.SpanContextFromContext(r.Context()); spanCtx.IsValid() {
fields = append(fields, FTraceID(spanCtx.TraceID().String()))
}
return With(l, fields...)
}

247
net/http/client.go Normal file
View File

@ -0,0 +1,247 @@
package http
import (
"context"
"crypto/tls"
"net"
"net/http"
"net/url"
"time"
"go.uber.org/zap"
"github.com/foomo/keel/net/http/roundtripware"
)
type HTTPClientOption func(*http.Client)
func HTTPClientWithTimeout(o time.Duration) HTTPClientOption {
return func(v *http.Client) {
v.Timeout = o
}
}
func HTTPClientWithJar(o http.CookieJar) HTTPClientOption {
return func(v *http.Client) {
v.Jar = o
}
}
func HTTPClientWithTransport(o http.RoundTripper) HTTPClientOption {
return func(v *http.Client) {
v.Transport = o
}
}
func HTTPClientWithCheckRedirect(o func(req *http.Request, via []*http.Request) error) HTTPClientOption {
return func(v *http.Client) {
v.CheckRedirect = o
}
}
func HTTPClientWithProxy(o func(request *http.Request) (*url.URL, error)) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.Proxy = o
v.Transport = t
}
}
}
func HTTPClientWithDialContext(o func(ctx context.Context, network, addr string) (net.Conn, error)) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.DialContext = o
v.Transport = t
}
}
}
func HTTPClientWithDialTLSContext(o func(ctx context.Context, network, addr string) (net.Conn, error)) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.DialTLSContext = o
v.Transport = t
}
}
}
func HTTPClientWithTLSClientConfig(o *tls.Config) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.TLSClientConfig = o
v.Transport = t
}
}
}
func HTTPClientWithTLSHandshakeTimeout(o time.Duration) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.TLSHandshakeTimeout = o
v.Transport = t
}
}
}
func HTTPClientWithDisableKeepAlives(o bool) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.DisableKeepAlives = o
v.Transport = t
}
}
}
func HTTPClientWithDisableCompression(o bool) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.DisableCompression = o
v.Transport = t
}
}
}
func HTTPClientWithMaxIdleConns(o int) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.MaxIdleConns = o
v.Transport = t
}
}
}
func HTTPClientWithMaxIdleConnsPerHost(o int) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.MaxIdleConnsPerHost = o
v.Transport = t
}
}
}
func HTTPClientWithMaxConnsPerHost(o int) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.MaxConnsPerHost = o
v.Transport = t
}
}
}
func HTTPClientWithIdleConnTimeout(o time.Duration) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.IdleConnTimeout = o
v.Transport = t
}
}
}
func HTTPClientWithResponseHeaderTimeout(o time.Duration) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.ResponseHeaderTimeout = o
v.Transport = t
}
}
}
func HTTPClientWithExpectContinueTimeout(o time.Duration) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.ExpectContinueTimeout = o
v.Transport = t
}
}
}
func HTTPClientWithTLSNextProto(o map[string]func(authority string, c *tls.Conn) http.RoundTripper) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.TLSNextProto = o
v.Transport = t
}
}
}
func HTTPClientWithProxyConnectHeader(o http.Header) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.ProxyConnectHeader = o
v.Transport = t
}
}
}
func HTTPClientWithGetProxyConnectHeader(o func(ctx context.Context, proxyURL *url.URL, target string) (http.Header, error)) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.GetProxyConnectHeader = o
v.Transport = t
}
}
}
func HTTPClientWithMaxResponseHeaderBytes(o int64) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.MaxResponseHeaderBytes = o
v.Transport = t
}
}
}
func HTTPClientWithWriteBufferSize(o int) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.WriteBufferSize = o
v.Transport = t
}
}
}
func HTTPClientWithReadBufferSize(o int) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.ReadBufferSize = o
v.Transport = t
}
}
}
func HTTPClientWithForceAttemptHTTP2(o bool) HTTPClientOption {
return func(v *http.Client) {
if t, ok := v.Transport.(*http.Transport); ok {
t.ForceAttemptHTTP2 = o
v.Transport = t
}
}
}
func HTTPClientWithRoundTripware(l *zap.Logger, roundTripware ...roundtripware.RoundTripware) HTTPClientOption {
return func(v *http.Client) {
v.Transport = roundtripware.NewRoundTripper(l, v.Transport, roundTripware...)
}
}
func NewHTTPClient(opts ...HTTPClientOption) *http.Client {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 45 * time.Second,
KeepAlive: 45 * time.Second,
}).DialContext,
DisableKeepAlives: true,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 5 * time.Second,
}
inst := &http.Client{
Transport: transport,
Timeout: 2 * time.Minute,
}
for _, opt := range opts {
opt(inst)
}
return inst
}

View File

@ -0,0 +1,39 @@
package roundtripware
import (
"fmt"
"net/http"
"go.uber.org/zap"
)
// DumpRequest returns a RoundTripper which prints out the request object
func DumpRequest() RoundTripware {
return func(l *zap.Logger, next Handler) Handler {
return func(req *http.Request) (*http.Response, error) {
if req.Header != nil && req.Header.Get("Content-Type") != "" {
var body string
if req.Body, body = readBodyPretty(req.Header.Get("Content-Type"), req.Body); body != "" {
fmt.Printf("Request %s:\n%s\n", req.URL, body)
}
}
return next(req)
}
}
}
// DumpResponse returns a RoundTripper which prints out the response object
func DumpResponse() RoundTripware {
return func(l *zap.Logger, next Handler) Handler {
return func(req *http.Request) (*http.Response, error) {
resp, err := next(req)
if resp.Header != nil && resp.Header.Get("Content-Type") != "" {
var body string
if resp.Body, body = readBodyPretty(resp.Header.Get("Content-Type"), resp.Body); body != "" {
fmt.Printf("Response %s:\n%s\n", req.URL, body)
}
}
return resp, err
}
}
}

View File

@ -0,0 +1,45 @@
package roundtripware
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"strings"
"github.com/tinylib/msgp/msgp"
)
func readBodyPretty(contentType string, original io.ReadCloser) (io.ReadCloser, string) {
var bs bytes.Buffer
var body string
defer func() {
_ = original.Close()
}()
// read in body
if _, err := io.Copy(&bs, original); err != nil {
return original, ""
} else {
body = bs.String()
}
switch {
case strings.HasPrefix(contentType, "application/json"):
var prettyJSON bytes.Buffer
if err := json.Indent(&prettyJSON, bs.Bytes(), "", " "); err == nil {
body = prettyJSON.String()
}
case strings.HasPrefix(contentType, "application/msgpack"):
var prettyJSON bytes.Buffer
var out bytes.Buffer
if _, err := msgp.UnmarshalAsJSON(&out, bs.Bytes()); err == nil {
if err := json.Indent(&prettyJSON, out.Bytes(), "", " "); err == nil {
body = prettyJSON.String()
}
}
}
// return copy of the original
return ioutil.NopCloser(strings.NewReader(bs.String())), body
}

View File

@ -0,0 +1,27 @@
package roundtripware
import (
"net/http"
"go.uber.org/zap"
"github.com/foomo/keel/log"
keeltime "github.com/foomo/keel/time"
)
// Logger returns a RoundTripware which logs all requests
func Logger() RoundTripware {
msg := "sent request"
return func(l *zap.Logger, next Handler) Handler {
return func(req *http.Request) (*http.Response, error) {
start := keeltime.Now()
resp, err := next(req)
log.WithHTTPRequestOut(l, req).Info(msg,
log.FDuration(keeltime.Now().Sub(start)),
log.FHTTPStatusCode(resp.StatusCode),
log.FHTTPRequestContentLength(resp.ContentLength),
)
return resp, err
}
}
}

View File

@ -0,0 +1,63 @@
package roundtripware
import (
"fmt"
"net/http"
"go.uber.org/zap"
"github.com/foomo/keel/log"
)
type (
RecoverOptions struct {
DisablePrintStack bool
}
RecoverOption func(options *RecoverOptions)
)
// GetDefaultRecoverOptions returns the default options
func GetDefaultRecoverOptions() RecoverOptions {
return RecoverOptions{
DisablePrintStack: false,
}
}
// RecoverWithDisablePrintStack roundTripware option
func RecoverWithDisablePrintStack(v bool) RecoverOption {
return func(o *RecoverOptions) {
o.DisablePrintStack = v
}
}
// Recover returns a RoundTripper which catches any panics
func Recover(opts ...RecoverOption) RoundTripware {
options := GetDefaultRecoverOptions()
for _, opt := range opts {
if opt != nil {
opt(&options)
}
}
return RecoverWithOptions(options)
}
// RecoverWithOptions returns a RoundTripper which catches any panics
func RecoverWithOptions(opts RecoverOptions) RoundTripware {
return func(l *zap.Logger, next Handler) Handler {
return func(req *http.Request) (*http.Response, error) {
defer func() {
if e := recover(); e != nil {
err, ok := e.(error)
if !ok {
err = fmt.Errorf("%v", e)
}
if !opts.DisablePrintStack {
l = l.With(log.FStackSkip(3))
}
log.WithError(l, err).Error("recovering from panic")
}
}()
return next(req)
}
}
}

View File

@ -0,0 +1,33 @@
package roundtripware
import (
"net/http"
"go.uber.org/zap"
)
type (
Handler func(r *http.Request) (*http.Response, error)
RoundTripware func(l *zap.Logger, next Handler) Handler
)
type RoundTripper struct {
http.RoundTripper
handler Handler
}
func NewRoundTripper(l *zap.Logger, parent http.RoundTripper, roundTripwares ...RoundTripware) *RoundTripper {
next := parent.RoundTrip
for _, roundTripware := range roundTripwares {
next = roundTripware(l, next)
}
return &RoundTripper{
RoundTripper: parent,
handler: next,
}
}
func (rt *RoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) {
return rt.handler(req)
}