-
Notifications
You must be signed in to change notification settings - Fork 213
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: http client integration #876
base: master
Are you sure you want to change the base?
Changes from 10 commits
6b48923
fd66575
439e98b
5f4c5ae
5214ea5
ed105c1
4e55daa
15aa59a
099dad4
7d9555d
f018dde
d6f2663
6af10bf
c5ad7e4
081fc0b
a3a6367
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
|
||
"github.com/getsentry/sentry-go" | ||
sentryhttpclient "github.com/getsentry/sentry-go/httpclient" | ||
) | ||
|
||
func main() { | ||
_ = sentry.Init(sentry.ClientOptions{ | ||
Dsn: "", | ||
EnableTracing: true, | ||
TracesSampleRate: 1.0, | ||
BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { | ||
fmt.Println(event) | ||
return event | ||
}, | ||
BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { | ||
fmt.Println(event) | ||
return event | ||
}, | ||
Debug: true, | ||
}) | ||
|
||
// With custom HTTP client | ||
ctx := sentry.SetHubOnContext(context.Background(), sentry.CurrentHub().Clone()) | ||
httpClient := &http.Client{ | ||
Transport: sentryhttpclient.NewSentryRoundTripper(nil), | ||
} | ||
|
||
err := getExamplePage(ctx, httpClient) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
// With Sentry's HTTP client | ||
err = getExamplePage(ctx, sentryhttpclient.SentryHTTPClient) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
func getExamplePage(ctx context.Context, httpClient *http.Client) error { | ||
span := sentry.StartSpan(ctx, "getExamplePage") | ||
ctx = span.Context() | ||
defer span.Finish() | ||
|
||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://example.com", nil) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
response, err := httpClient.Do(request) | ||
if err != nil { | ||
return err | ||
} | ||
defer func() { | ||
if response.Body != nil { | ||
_ = response.Body.Close() | ||
} | ||
}() | ||
|
||
body, err := io.ReadAll(response.Body) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
fmt.Println(string(body)) | ||
|
||
return nil | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,107 @@ | ||||||||||
// Package sentryhttpclient provides Sentry integration for Requests modules to enable distributed tracing between services. | ||||||||||
// It is compatible with `net/http.RoundTripper`. | ||||||||||
// | ||||||||||
// import sentryhttpclient "github.com/getsentry/sentry-go/httpclient" | ||||||||||
// | ||||||||||
// roundTrippper := sentryhttpclient.NewSentryRoundTripper(nil, nil) | ||||||||||
// client := &http.Client{ | ||||||||||
// Transport: roundTripper, | ||||||||||
// } | ||||||||||
// | ||||||||||
// request, err := client.Do(request) | ||||||||||
package sentryhttpclient | ||||||||||
|
||||||||||
import ( | ||||||||||
"fmt" | ||||||||||
"net/http" | ||||||||||
|
||||||||||
"github.com/getsentry/sentry-go" | ||||||||||
) | ||||||||||
|
||||||||||
// SentryRoundTripTracerOption provides a specific type in which defines the option for SentryRoundTripper. | ||||||||||
type SentryRoundTripTracerOption func(*SentryRoundTripper) | ||||||||||
|
||||||||||
// WithTags allows the RoundTripper to includes additional tags. | ||||||||||
func WithTags(tags map[string]string) SentryRoundTripTracerOption { | ||||||||||
return func(t *SentryRoundTripper) { | ||||||||||
for k, v := range tags { | ||||||||||
t.tags[k] = v | ||||||||||
} | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
// WithTag allows the RoundTripper to includes additional tag. | ||||||||||
func WithTag(key, value string) SentryRoundTripTracerOption { | ||||||||||
return func(t *SentryRoundTripper) { | ||||||||||
t.tags[key] = value | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
// NewSentryRoundTripper provides a wrapper to existing http.RoundTripper to have required span data and trace headers for outgoing HTTP requests. | ||||||||||
// | ||||||||||
// - If `nil` is passed to `originalRoundTripper`, it will use http.DefaultTransport instead. | ||||||||||
func NewSentryRoundTripper(originalRoundTripper http.RoundTripper, opts ...SentryRoundTripTracerOption) http.RoundTripper { | ||||||||||
if originalRoundTripper == nil { | ||||||||||
originalRoundTripper = http.DefaultTransport | ||||||||||
} | ||||||||||
|
||||||||||
t := &SentryRoundTripper{ | ||||||||||
originalRoundTripper: originalRoundTripper, | ||||||||||
tags: make(map[string]string), | ||||||||||
} | ||||||||||
|
||||||||||
for _, opt := range opts { | ||||||||||
if opt != nil { | ||||||||||
opt(t) | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
return t | ||||||||||
} | ||||||||||
|
||||||||||
// SentryRoundTripper provides a http.RoundTripper implementation for Sentry Requests module. | ||||||||||
type SentryRoundTripper struct { | ||||||||||
originalRoundTripper http.RoundTripper | ||||||||||
|
||||||||||
tags map[string]string | ||||||||||
} | ||||||||||
|
||||||||||
func (s *SentryRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) { | ||||||||||
// Only create the `http.client` span only if there is a parent span. | ||||||||||
parentSpan := sentry.GetSpanFromContext(request.Context()) | ||||||||||
if parentSpan == nil { | ||||||||||
return s.originalRoundTripper.RoundTrip(request) | ||||||||||
} | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A quick note why I prefer this instead of creating a I used the JS SDK a lot at work lately, and I've been wondering the entire week, on their Should the Go SDK move forward with that, as a We could to the Lines 196 to 199 in a6acd05
Although we could just not call the |
||||||||||
|
||||||||||
cleanRequestURL := request.URL.Redacted() | ||||||||||
|
||||||||||
span := parentSpan.StartChild("http.client", sentry.WithTransactionName(fmt.Sprintf("%s %s", request.Method, cleanRequestURL))) | ||||||||||
span.Tags = s.tags | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we setting span tags here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it not ideal? Should I remove it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What info is in there? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Decided to just remove it. |
||||||||||
defer span.Finish() | ||||||||||
|
||||||||||
span.SetData("http.query", request.URL.Query().Encode()) | ||||||||||
span.SetData("http.fragment", request.URL.Fragment) | ||||||||||
span.SetData("http.request.method", request.Method) | ||||||||||
span.SetData("server.address", request.URL.Hostname()) | ||||||||||
span.SetData("server.port", request.URL.Port()) | ||||||||||
|
||||||||||
// Always add `Baggage` and `Sentry-Trace` headers. | ||||||||||
request.Header.Add("Baggage", span.ToBaggage()) | ||||||||||
request.Header.Add("Sentry-Trace", span.ToSentryTrace()) | ||||||||||
Comment on lines
+118
to
+120
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To support the recently added "Tracing without Performance" feature, we should use |
||||||||||
|
||||||||||
response, err := s.originalRoundTripper.RoundTrip(request) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We also want to handle the err case |
||||||||||
|
||||||||||
if response != nil { | ||||||||||
span.Status = sentry.HTTPtoSpanStatus(response.StatusCode) | ||||||||||
span.SetData("http.response.status_code", response.StatusCode) | ||||||||||
span.SetData("http.response_content_length", response.ContentLength) | ||||||||||
} | ||||||||||
|
||||||||||
return response, err | ||||||||||
} | ||||||||||
|
||||||||||
// SentryHTTPClient provides a default HTTP client with SentryRoundTripper included. | ||||||||||
// This can be used directly to perform HTTP request. | ||||||||||
var SentryHTTPClient = &http.Client{ | ||||||||||
Transport: NewSentryRoundTripper(http.DefaultTransport), | ||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to add a new
ClientOption
for https://develop.sentry.dev/sdk/telemetry/traces/#tracepropagationtargetsThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I wrote this, the docs said trace propagation targets is only for avoiding CORS-related issue, and since the dotnet SDK does not have that option too, I don't implement it here.
But is my assumption wrong here? Should all SDK implements trace propagation targets then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes