120 lines
4.1 KiB
Go
120 lines
4.1 KiB
Go
package util
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
|
|
"github.com/getsentry/sentry-go/internal/debuglog"
|
|
"github.com/getsentry/sentry-go/internal/protocol"
|
|
"github.com/getsentry/sentry-go/internal/ratelimit"
|
|
)
|
|
|
|
// MaxDrainResponseBytes is the maximum number of bytes that transport
|
|
// implementations will read from response bodies when draining them.
|
|
const MaxDrainResponseBytes = 16 << 10
|
|
|
|
// HandleHTTPResponse is a helper method that reads the HTTP response and handles debug output.
|
|
func HandleHTTPResponse(response *http.Response, identifier string) bool {
|
|
if response.StatusCode >= 200 && response.StatusCode < 300 {
|
|
return true
|
|
}
|
|
|
|
if response.StatusCode >= 400 && response.StatusCode <= 599 {
|
|
body, err := io.ReadAll(io.LimitReader(response.Body, MaxDrainResponseBytes))
|
|
if err != nil {
|
|
debuglog.Printf("Error while reading response body: %v", err)
|
|
return false
|
|
}
|
|
|
|
switch {
|
|
case response.StatusCode == http.StatusRequestEntityTooLarge:
|
|
debuglog.Printf("Sending %s failed because the request was too large: %s", identifier, string(body))
|
|
case response.StatusCode >= 500:
|
|
debuglog.Printf("Sending %s failed with server error %d: %s", identifier, response.StatusCode, string(body))
|
|
default:
|
|
debuglog.Printf("Sending %s failed with client error %d: %s", identifier, response.StatusCode, string(body))
|
|
}
|
|
return false
|
|
}
|
|
|
|
debuglog.Printf("Unexpected status code %d for event %s", response.StatusCode, identifier)
|
|
return false
|
|
}
|
|
|
|
// EnvelopeIdentifier returns a human-readable identifier for the event to be used in log messages.
|
|
// Format: "<description> [<event-id>]".
|
|
func EnvelopeIdentifier(envelope *protocol.Envelope) string {
|
|
if envelope == nil || len(envelope.Items) == 0 {
|
|
return "empty envelope"
|
|
}
|
|
|
|
var description string
|
|
// we don't currently support mixed envelope types, so all event types would have the same type.
|
|
itemType := envelope.Items[0].Header.Type
|
|
|
|
switch itemType {
|
|
case protocol.EnvelopeItemTypeEvent:
|
|
description = "error"
|
|
case protocol.EnvelopeItemTypeTransaction:
|
|
description = "transaction"
|
|
case protocol.EnvelopeItemTypeCheckIn:
|
|
description = "check-in"
|
|
case protocol.EnvelopeItemTypeLog:
|
|
logCount := 0
|
|
for _, item := range envelope.Items {
|
|
if item != nil && item.Header != nil && item.Header.Type == protocol.EnvelopeItemTypeLog && item.Header.ItemCount != nil {
|
|
logCount += *item.Header.ItemCount
|
|
}
|
|
}
|
|
description = fmt.Sprintf("%d log events", logCount)
|
|
case protocol.EnvelopeItemTypeTraceMetric:
|
|
metricCount := 0
|
|
for _, item := range envelope.Items {
|
|
if item != nil && item.Header != nil && item.Header.Type == protocol.EnvelopeItemTypeTraceMetric && item.Header.ItemCount != nil {
|
|
metricCount += *item.Header.ItemCount
|
|
}
|
|
}
|
|
description = fmt.Sprintf("%d metric events", metricCount)
|
|
default:
|
|
description = fmt.Sprintf("%s event", itemType)
|
|
}
|
|
|
|
return fmt.Sprintf("%s [%s]", description, envelope.Header.EventID)
|
|
}
|
|
|
|
// SendResult holds the outcome of an HTTP request sent to Sentry.
|
|
type SendResult struct {
|
|
Success bool
|
|
StatusCode int
|
|
Limits ratelimit.Map
|
|
}
|
|
|
|
// IsSendError returns true if the response indicates a server/client error that should be recorded
|
|
// as send_error for client report outcomes (non-429 failures).
|
|
func (r *SendResult) IsSendError() bool {
|
|
return !r.Success && r.StatusCode != http.StatusTooManyRequests
|
|
}
|
|
|
|
// DoSendRequest executes an HTTP request, handles response logging, extracts rate limits, and
|
|
// drains+closes the response body.
|
|
func DoSendRequest(client *http.Client, request *http.Request, identifier string) (*SendResult, error) {
|
|
response, err := client.Do(request) //nolint:gosec // G704: request is constructed internally, not from user input
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
success := HandleHTTPResponse(response, identifier)
|
|
limits := ratelimit.FromResponse(response)
|
|
|
|
// Drain body up to a limit and close it, allowing the transport to reuse TCP connections.
|
|
_, _ = io.CopyN(io.Discard, response.Body, MaxDrainResponseBytes)
|
|
response.Body.Close()
|
|
|
|
return &SendResult{
|
|
Success: success,
|
|
StatusCode: response.StatusCode,
|
|
Limits: limits,
|
|
}, nil
|
|
}
|