From 61f221beb4ee7bfaaf5cbd24cd3c9e49ee80a8c4 Mon Sep 17 00:00:00 2001 From: Matthias Kaeppler Date: Wed, 23 Feb 2022 15:33:47 +0100 Subject: [PATCH] Add roundtripper to log outbound HTTP requests This is useful when sending requests to external parties such as Object Storage providers. --- log/outbound_http.go | 34 +++++++++++++++++++++++++++ log/outbound_http_options.go | 27 ++++++++++++++++++++++ log/outbound_http_test.go | 45 ++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 log/outbound_http.go create mode 100644 log/outbound_http_options.go create mode 100644 log/outbound_http_test.go diff --git a/log/outbound_http.go b/log/outbound_http.go new file mode 100644 index 00000000..a50ba59f --- /dev/null +++ b/log/outbound_http.go @@ -0,0 +1,34 @@ +package log + +import ( + "net/http" + "time" +) + +type loggingRoundTripper struct { + delegate http.RoundTripper + config loggingRoundTripperConfig +} + +func (l loggingRoundTripper) RoundTrip(req *http.Request) (res *http.Response, e error) { + started := time.Now() + logger := l.config.logger + + defer func() { + duration := time.Since(started) + + fields := Fields{} + fields[httpRequestMethodField] = req.Method + fields[httpResponseStatusCodeField] = res.StatusCode + fields[requestDurationField] = int64(duration / time.Millisecond) + + logger.WithFields(fields).Info("outbound_http") + }() + + return l.delegate.RoundTrip(req) +} + +func NewLoggingRoundTripper(delegate http.RoundTripper, opts ...LoggingRoundTripperOption) http.RoundTripper { + config := applyLoggingRoundTripperOptions(opts) + return &loggingRoundTripper{delegate: delegate, config: config} +} diff --git a/log/outbound_http_options.go b/log/outbound_http_options.go new file mode 100644 index 00000000..dc5865d0 --- /dev/null +++ b/log/outbound_http_options.go @@ -0,0 +1,27 @@ +package log + +import "github.com/sirupsen/logrus" + +type loggingRoundTripperConfig struct { + logger *logrus.Logger +} + +// LoggingRoundTripperOption will configure a correlation handler. +// Currently there are no options, but this gives us the option +// to extend the interface in a backwards compatible way. +type LoggingRoundTripperOption func(*loggingRoundTripperConfig) + +func applyLoggingRoundTripperOptions(opts []LoggingRoundTripperOption) loggingRoundTripperConfig { + config := loggingRoundTripperConfig{} + for _, v := range opts { + v(&config) + } + + return config +} + +func WithRoundTripLogger(logger *logrus.Logger) LoggingRoundTripperOption { + return func(config *loggingRoundTripperConfig) { + config.logger = logger + } +} diff --git a/log/outbound_http_test.go b/log/outbound_http_test.go new file mode 100644 index 00000000..9f42912d --- /dev/null +++ b/log/outbound_http_test.go @@ -0,0 +1,45 @@ +package log + +import ( + "bytes" + "net/http" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_logRequest(t *testing.T) { + require := require.New(t) + + req, err := http.NewRequest("GET", "http://example.com", nil) + require.NoError(err) + + mockTransport := roundTripperFunc(func(req *http.Request) (*http.Response, error) { + return &http.Response{StatusCode: 200}, nil + }) + + buf := &bytes.Buffer{} + closer, err := Initialize(WithWriter(buf)) + require.NoError(err) + defer closer.Close() + + roundTripper := NewLoggingRoundTripper(mockTransport, func(lrtc *loggingRoundTripperConfig) { + lrtc.logger = logger + }) + + roundTripper.RoundTrip(req) + + require.Regexp(`^.*level=info msg=outbound_http duration_ms=\d+ method=GET status=200\n$`, buf.String()) +} + +type delegatedRoundTripper struct { + delegate func(req *http.Request) (*http.Response, error) +} + +func (c delegatedRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + return c.delegate(req) +} + +func roundTripperFunc(delegate func(req *http.Request) (*http.Response, error)) http.RoundTripper { + return &delegatedRoundTripper{delegate} +} -- GitLab