diff --git a/cmd/fastly-exporter/main.go b/cmd/fastly-exporter/main.go index 4a5c708..bf45eb1 100644 --- a/cmd/fastly-exporter/main.go +++ b/cmd/fastly-exporter/main.go @@ -221,10 +221,16 @@ func main() { apiLogger = log.With(logger, "component", "api.fastly.com") } + var userAgent string + { + userAgent = `Fastly-Exporter (` + programVersion + `)` + } + var apiClient *http.Client { apiClient = &http.Client{ - Timeout: apiTimeout, + Timeout: apiTimeout, + Transport: userAgentTransport(http.DefaultTransport, userAgent), } } @@ -289,11 +295,10 @@ func main() { { var ( rtLogger = log.With(logger, "component", "rt.fastly.com") - rtClient = &http.Client{Timeout: rtTimeout} + rtClient = &http.Client{Timeout: rtTimeout, Transport: userAgentTransport(http.DefaultTransport, userAgent)} subscriberOptions = []rt.SubscriberOption{ rt.WithLogger(rtLogger), rt.WithMetadataProvider(serviceCache), - rt.WithUserAgent(`Fastly-Exporter (` + programVersion + `)`), } ) manager = rt.NewManager(serviceCache, rtClient, token, registry, subscriberOptions, rtLogger) diff --git a/cmd/fastly-exporter/transport.go b/cmd/fastly-exporter/transport.go new file mode 100644 index 0000000..d456495 --- /dev/null +++ b/cmd/fastly-exporter/transport.go @@ -0,0 +1,14 @@ +package main + +import "net/http" + +type roundTripperFunc func(*http.Request) (*http.Response, error) + +func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { return f(req) } + +func userAgentTransport(next http.RoundTripper, userAgent string) http.RoundTripper { + return roundTripperFunc(func(req *http.Request) (*http.Response, error) { + req.Header.Set("User-Agent", userAgent) + return next.RoundTrip(req) + }) +} diff --git a/cmd/fastly-exporter/transport_test.go b/cmd/fastly-exporter/transport_test.go new file mode 100644 index 0000000..8887f8d --- /dev/null +++ b/cmd/fastly-exporter/transport_test.go @@ -0,0 +1,25 @@ +package main + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestUserAgentTransport(t *testing.T) { + c := make(chan string, 1) + handler := func(_ http.ResponseWriter, r *http.Request) { c <- r.Header.Get("User-Agent") } + server := httptest.NewServer(http.HandlerFunc(handler)) + defer server.Close() + + userAgent := "something" + transport := userAgentTransport(http.DefaultTransport, userAgent) + client := &http.Client{Transport: transport} + if _, err := client.Get(server.URL); err != nil { + t.Fatal(err) + } + + if want, have := userAgent, <-c; want != have { + t.Fatalf("want %q, have %q", want, have) + } +} diff --git a/pkg/rt/subscriber.go b/pkg/rt/subscriber.go index 8f4a02b..76b2cea 100644 --- a/pkg/rt/subscriber.go +++ b/pkg/rt/subscriber.go @@ -32,7 +32,6 @@ type MetadataProvider interface { // It emits the received real-time stats data to Prometheus. type Subscriber struct { client HTTPClient - userAgent string token string serviceID string provider MetadataProvider @@ -44,12 +43,6 @@ type Subscriber struct { // SubscriberOption provides some additional behavior to a subscriber. type SubscriberOption func(*Subscriber) -// WithUserAgent sets the User-Agent supplied to rt.fastly.com. -// By default, the DefaultUserAgent is used. -func WithUserAgent(ua string) SubscriberOption { - return func(s *Subscriber) { s.userAgent = ua } -} - // WithMetadataProvider sets the resolver used to look up service names and // versions. By default, a no-op metadata resolver is used, which causes each // service to have its name set to its service ID, and its version set to @@ -71,16 +64,11 @@ func WithPostprocess(f func()) SubscriberOption { return func(s *Subscriber) { s.postprocess = f } } -// DefaultUserAgent passed to rt.fastly.com. -// To change, use the WithUserAgent option. -const DefaultUserAgent = "Fastly-Exporter (unknown version)" - // NewSubscriber returns a ready-to-use subscriber. // Run must be called to update the metrics. func NewSubscriber(client HTTPClient, token, serviceID string, metrics *gen.Metrics, options ...SubscriberOption) *Subscriber { s := &Subscriber{ client: client, - userAgent: DefaultUserAgent, token: token, serviceID: serviceID, metrics: metrics, @@ -147,7 +135,6 @@ func (s *Subscriber) query(ctx context.Context, ts uint64) (currentName string, return name, apiResultError, 0, ts, fmt.Errorf("error constructing real-time stats API request: %w", err) } - req.Header.Set("User-Agent", s.userAgent) req.Header.Set("Fastly-Key", s.token) req.Header.Set("Accept", "application/json") resp, err := s.client.Do(req.WithContext(ctx)) diff --git a/pkg/rt/subscriber_test.go b/pkg/rt/subscriber_test.go index a6c0011..44ab996 100644 --- a/pkg/rt/subscriber_test.go +++ b/pkg/rt/subscriber_test.go @@ -79,25 +79,6 @@ func TestSubscriberNoData(t *testing.T) { assertMetricOutput(t, want, have) } -func TestUserAgent(t *testing.T) { - var ( - client = newMockRealtimeClient(`{}`) - userAgent = "Some user agent string" - metrics = gen.NewMetrics("ns", "ss", filter.Filter{}, prometheus.NewRegistry()) - processed = make(chan struct{}) - postprocess = func() { close(processed) } - options = []rt.SubscriberOption{rt.WithUserAgent(userAgent), rt.WithPostprocess(postprocess)} - subscriber = rt.NewSubscriber(client, "token", "service_id", metrics, options...) - ) - go subscriber.Run(context.Background()) - - <-processed - - if want, have := userAgent, client.lastUserAgent; want != have { - t.Errorf("User-Agent: want %q, have %q", want, have) - } -} - func TestBadTokenNoSpam(t *testing.T) { var ( client = &countingRealtimeClient{code: 403, response: `{"Error": "unauthorized"}`}