fix(go.sum): update ResolveSpec dependency to v1.0.87
This commit is contained in:
+603
@@ -0,0 +1,603 @@
|
||||
package sentry
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"maps"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go/attribute"
|
||||
"github.com/getsentry/sentry-go/internal/debuglog"
|
||||
"github.com/getsentry/sentry-go/internal/ratelimit"
|
||||
"github.com/getsentry/sentry-go/report"
|
||||
)
|
||||
|
||||
// Scope holds contextual data for the current scope.
|
||||
//
|
||||
// The scope is an object that can cloned efficiently and stores data that is
|
||||
// locally relevant to an event. For instance the scope will hold recorded
|
||||
// breadcrumbs and similar information.
|
||||
//
|
||||
// The scope can be interacted with in two ways. First, the scope is routinely
|
||||
// updated with information by functions such as AddBreadcrumb which will modify
|
||||
// the current scope. Second, the current scope can be configured through the
|
||||
// ConfigureScope function or Hub method of the same name.
|
||||
//
|
||||
// The scope is meant to be modified but not inspected directly. When preparing
|
||||
// an event for reporting, the current client adds information from the current
|
||||
// scope into the event.
|
||||
type Scope struct {
|
||||
mu sync.RWMutex
|
||||
attributes map[string]attribute.Value
|
||||
breadcrumbs []*Breadcrumb
|
||||
attachments []*Attachment
|
||||
user User
|
||||
tags map[string]string
|
||||
contexts map[string]Context
|
||||
fingerprint []string
|
||||
level Level
|
||||
request *http.Request
|
||||
// requestBody holds a reference to the original request.Body.
|
||||
requestBody interface {
|
||||
// Bytes returns bytes from the original body, lazily buffered as the
|
||||
// original body is read.
|
||||
Bytes() []byte
|
||||
// Overflow returns true if the body is larger than the maximum buffer
|
||||
// size.
|
||||
Overflow() bool
|
||||
}
|
||||
eventProcessors []EventProcessor
|
||||
|
||||
propagationContext PropagationContext
|
||||
span *Span
|
||||
}
|
||||
|
||||
// NewScope creates a new Scope.
|
||||
func NewScope() *Scope {
|
||||
return &Scope{
|
||||
attributes: make(map[string]attribute.Value),
|
||||
breadcrumbs: make([]*Breadcrumb, 0),
|
||||
attachments: make([]*Attachment, 0),
|
||||
tags: make(map[string]string),
|
||||
contexts: make(map[string]Context),
|
||||
fingerprint: make([]string, 0),
|
||||
propagationContext: NewPropagationContext(),
|
||||
}
|
||||
}
|
||||
|
||||
// AddBreadcrumb adds new breadcrumb to the current scope
|
||||
// and optionally throws the old one if limit is reached.
|
||||
func (scope *Scope) AddBreadcrumb(breadcrumb *Breadcrumb, limit int) {
|
||||
if breadcrumb.Timestamp.IsZero() {
|
||||
breadcrumb.Timestamp = time.Now()
|
||||
}
|
||||
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
scope.breadcrumbs = append(scope.breadcrumbs, breadcrumb)
|
||||
if len(scope.breadcrumbs) > limit {
|
||||
scope.breadcrumbs = scope.breadcrumbs[1 : limit+1]
|
||||
}
|
||||
}
|
||||
|
||||
// ClearBreadcrumbs clears all breadcrumbs from the current scope.
|
||||
func (scope *Scope) ClearBreadcrumbs() {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
scope.breadcrumbs = []*Breadcrumb{}
|
||||
}
|
||||
|
||||
// AddAttachment adds new attachment to the current scope.
|
||||
func (scope *Scope) AddAttachment(attachment *Attachment) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
scope.attachments = append(scope.attachments, attachment)
|
||||
}
|
||||
|
||||
// ClearAttachments clears all attachments from the current scope.
|
||||
func (scope *Scope) ClearAttachments() {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
scope.attachments = []*Attachment{}
|
||||
}
|
||||
|
||||
// SetUser sets the user for the current scope.
|
||||
func (scope *Scope) SetUser(user User) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
scope.user = user
|
||||
}
|
||||
|
||||
// SetRequest sets the request for the current scope.
|
||||
func (scope *Scope) SetRequest(r *http.Request) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
scope.request = r
|
||||
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Don't buffer request body if we know it is oversized.
|
||||
if r.ContentLength > maxRequestBodyBytes {
|
||||
return
|
||||
}
|
||||
// Don't buffer if there is no body.
|
||||
if r.Body == nil || r.Body == http.NoBody {
|
||||
return
|
||||
}
|
||||
buf := &limitedBuffer{Capacity: maxRequestBodyBytes}
|
||||
r.Body = readCloser{
|
||||
Reader: io.TeeReader(r.Body, buf),
|
||||
Closer: r.Body,
|
||||
}
|
||||
scope.requestBody = buf
|
||||
}
|
||||
|
||||
// SetRequestBody sets the request body for the current scope.
|
||||
//
|
||||
// This method should only be called when the body bytes are already available
|
||||
// in memory. Typically, the request body is buffered lazily from the
|
||||
// Request.Body from SetRequest.
|
||||
func (scope *Scope) SetRequestBody(b []byte) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
capacity := maxRequestBodyBytes
|
||||
overflow := false
|
||||
if len(b) > capacity {
|
||||
overflow = true
|
||||
b = b[:capacity]
|
||||
}
|
||||
scope.requestBody = &limitedBuffer{
|
||||
Capacity: capacity,
|
||||
Buffer: *bytes.NewBuffer(b),
|
||||
overflow: overflow,
|
||||
}
|
||||
}
|
||||
|
||||
// maxRequestBodyBytes is the default maximum request body size to send to
|
||||
// Sentry.
|
||||
const maxRequestBodyBytes = 10 * 1024
|
||||
|
||||
// A limitedBuffer is like a bytes.Buffer, but limited to store at most Capacity
|
||||
// bytes. Any writes past the capacity are silently discarded, similar to
|
||||
// io.Discard.
|
||||
type limitedBuffer struct {
|
||||
Capacity int
|
||||
|
||||
bytes.Buffer
|
||||
overflow bool
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (b *limitedBuffer) Write(p []byte) (n int, err error) {
|
||||
// Silently ignore writes after overflow.
|
||||
if b.overflow {
|
||||
return len(p), nil
|
||||
}
|
||||
left := b.Capacity - b.Len()
|
||||
if left < 0 {
|
||||
left = 0
|
||||
}
|
||||
if len(p) > left {
|
||||
b.overflow = true
|
||||
p = p[:left]
|
||||
}
|
||||
return b.Buffer.Write(p)
|
||||
}
|
||||
|
||||
// Overflow returns true if the limitedBuffer discarded bytes written to it.
|
||||
func (b *limitedBuffer) Overflow() bool {
|
||||
return b.overflow
|
||||
}
|
||||
|
||||
// readCloser combines an io.Reader and an io.Closer to implement io.ReadCloser.
|
||||
type readCloser struct {
|
||||
io.Reader
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// SetAttributes adds attributes to the current scope.
|
||||
func (scope *Scope) SetAttributes(attrs ...attribute.Builder) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
for _, a := range attrs {
|
||||
if a.Value.Type() == attribute.INVALID {
|
||||
debuglog.Printf("invalid attribute: %v", a)
|
||||
continue
|
||||
}
|
||||
scope.attributes[a.Key] = a.Value
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveAttribute removes an attribute from the current scope.
|
||||
func (scope *Scope) RemoveAttribute(key string) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
delete(scope.attributes, key)
|
||||
}
|
||||
|
||||
// SetTag adds a tag to the current scope.
|
||||
func (scope *Scope) SetTag(key, value string) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
scope.tags[key] = value
|
||||
}
|
||||
|
||||
// SetTags assigns multiple tags to the current scope.
|
||||
func (scope *Scope) SetTags(tags map[string]string) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
for k, v := range tags {
|
||||
scope.tags[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveTag removes a tag from the current scope.
|
||||
func (scope *Scope) RemoveTag(key string) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
delete(scope.tags, key)
|
||||
}
|
||||
|
||||
// SetContext adds a context to the current scope.
|
||||
func (scope *Scope) SetContext(key string, value Context) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
scope.contexts[key] = value
|
||||
}
|
||||
|
||||
// SetContexts assigns multiple contexts to the current scope.
|
||||
func (scope *Scope) SetContexts(contexts map[string]Context) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
for k, v := range contexts {
|
||||
scope.contexts[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveContext removes a context from the current scope.
|
||||
func (scope *Scope) RemoveContext(key string) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
delete(scope.contexts, key)
|
||||
}
|
||||
|
||||
// SetFingerprint sets new fingerprint for the current scope.
|
||||
func (scope *Scope) SetFingerprint(fingerprint []string) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
scope.fingerprint = fingerprint
|
||||
}
|
||||
|
||||
// SetLevel sets new level for the current scope.
|
||||
func (scope *Scope) SetLevel(level Level) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
scope.level = level
|
||||
}
|
||||
|
||||
// SetPropagationContext sets the propagation context for the current scope.
|
||||
func (scope *Scope) SetPropagationContext(propagationContext PropagationContext) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
scope.propagationContext = propagationContext
|
||||
}
|
||||
|
||||
func (scope *Scope) propagationContextSnapshot() PropagationContext {
|
||||
scope.mu.RLock()
|
||||
defer scope.mu.RUnlock()
|
||||
|
||||
return scope.propagationContext
|
||||
}
|
||||
|
||||
// GetSpan returns the span from the current scope.
|
||||
func (scope *Scope) GetSpan() *Span {
|
||||
scope.mu.RLock()
|
||||
defer scope.mu.RUnlock()
|
||||
|
||||
return scope.span
|
||||
}
|
||||
|
||||
// SetSpan sets a span for the current scope.
|
||||
func (scope *Scope) SetSpan(span *Span) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
scope.span = span
|
||||
}
|
||||
|
||||
// Clone returns a copy of the current scope with all data copied over.
|
||||
func (scope *Scope) Clone() *Scope {
|
||||
scope.mu.RLock()
|
||||
defer scope.mu.RUnlock()
|
||||
|
||||
clone := NewScope()
|
||||
clone.user = scope.user
|
||||
clone.breadcrumbs = make([]*Breadcrumb, len(scope.breadcrumbs))
|
||||
copy(clone.breadcrumbs, scope.breadcrumbs)
|
||||
clone.attachments = make([]*Attachment, len(scope.attachments))
|
||||
copy(clone.attachments, scope.attachments)
|
||||
clone.attributes = maps.Clone(scope.attributes)
|
||||
clone.contexts = maps.Clone(scope.contexts)
|
||||
clone.tags = maps.Clone(scope.tags)
|
||||
clone.fingerprint = make([]string, len(scope.fingerprint))
|
||||
copy(clone.fingerprint, scope.fingerprint)
|
||||
clone.level = scope.level
|
||||
clone.request = scope.request
|
||||
clone.requestBody = scope.requestBody
|
||||
clone.eventProcessors = scope.eventProcessors
|
||||
clone.propagationContext = scope.propagationContext
|
||||
clone.span = scope.span
|
||||
return clone
|
||||
}
|
||||
|
||||
// Clear removes the data from the current scope. Not safe for concurrent use.
|
||||
func (scope *Scope) Clear() {
|
||||
*scope = *NewScope()
|
||||
}
|
||||
|
||||
// AddEventProcessor adds an event processor to the current scope.
|
||||
func (scope *Scope) AddEventProcessor(processor EventProcessor) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
scope.eventProcessors = append(scope.eventProcessors, processor)
|
||||
}
|
||||
|
||||
// ApplyToEvent takes the data from the current scope and attaches it to the event.
|
||||
func (scope *Scope) ApplyToEvent(event *Event, hint *EventHint, client *Client) *Event { //nolint:gocyclo
|
||||
scope.mu.RLock()
|
||||
defer scope.mu.RUnlock()
|
||||
|
||||
if len(scope.breadcrumbs) > 0 {
|
||||
event.Breadcrumbs = append(event.Breadcrumbs, scope.breadcrumbs...)
|
||||
}
|
||||
|
||||
if len(scope.attachments) > 0 {
|
||||
event.Attachments = append(event.Attachments, scope.attachments...)
|
||||
}
|
||||
|
||||
if len(scope.tags) > 0 {
|
||||
if event.Tags == nil {
|
||||
event.Tags = make(map[string]string, len(scope.tags))
|
||||
}
|
||||
|
||||
for key, value := range scope.tags {
|
||||
event.Tags[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
if len(scope.contexts) > 0 {
|
||||
if event.Contexts == nil {
|
||||
event.Contexts = make(map[string]Context)
|
||||
}
|
||||
|
||||
for key, value := range scope.contexts {
|
||||
if key == "trace" && event.Type == transactionType {
|
||||
// Do not override trace context of
|
||||
// transactions, otherwise it breaks the
|
||||
// transaction event representation.
|
||||
// For error events, the trace context is used
|
||||
// to link errors and traces/spans in Sentry.
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure we are not overwriting event fields
|
||||
if _, ok := event.Contexts[key]; !ok {
|
||||
event.Contexts[key] = cloneContext(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if event.Contexts == nil {
|
||||
event.Contexts = make(map[string]Context)
|
||||
}
|
||||
|
||||
if scope.span != nil {
|
||||
if _, ok := event.Contexts["trace"]; !ok {
|
||||
event.Contexts["trace"] = scope.span.traceContext().Map()
|
||||
}
|
||||
|
||||
transaction := scope.span.GetTransaction()
|
||||
if transaction != nil {
|
||||
event.sdkMetaData.dsc = DynamicSamplingContextFromTransaction(transaction)
|
||||
}
|
||||
} else {
|
||||
event.Contexts["trace"] = scope.propagationContext.Map()
|
||||
|
||||
dsc := scope.propagationContext.DynamicSamplingContext
|
||||
if !dsc.HasEntries() && client != nil {
|
||||
dsc = DynamicSamplingContextFromScope(scope, client)
|
||||
}
|
||||
event.sdkMetaData.dsc = dsc
|
||||
}
|
||||
|
||||
// If an external trace resolver is registered (e.g. OTel), override
|
||||
// trace/span IDs from the hint context or the scope's request context.
|
||||
if client != nil {
|
||||
var ctx context.Context
|
||||
if hint != nil {
|
||||
ctx = hint.Context
|
||||
}
|
||||
if ctx == nil && scope.request != nil {
|
||||
ctx = scope.request.Context()
|
||||
}
|
||||
if traceID, spanID, ok := client.externalTraceContextFromContext(ctx); event.Type != transactionType && ok {
|
||||
traceCtx := event.Contexts["trace"]
|
||||
traceCtx["trace_id"] = traceID.String()
|
||||
traceCtx["span_id"] = spanID.String()
|
||||
}
|
||||
}
|
||||
|
||||
if event.User.IsEmpty() {
|
||||
event.User = scope.user
|
||||
}
|
||||
|
||||
if len(event.Fingerprint) == 0 {
|
||||
event.Fingerprint = append(event.Fingerprint, scope.fingerprint...)
|
||||
}
|
||||
|
||||
if scope.level != "" {
|
||||
event.Level = scope.level
|
||||
}
|
||||
|
||||
if event.Request == nil && scope.request != nil {
|
||||
event.Request = newRequest(scope.request, sendDefaultPIIEnabled(client))
|
||||
// NOTE: The SDK does not attempt to send partial request body data.
|
||||
//
|
||||
// The reason being that Sentry's ingest pipeline and UI are optimized
|
||||
// to show structured data. Additionally, tooling around PII scrubbing
|
||||
// relies on structured data; truncated request bodies would create
|
||||
// invalid payloads that are more prone to leaking PII data.
|
||||
//
|
||||
// Users can still send more data along their events if they want to,
|
||||
// for example using Event.Contexts.
|
||||
if scope.requestBody != nil && !scope.requestBody.Overflow() {
|
||||
event.Request.Data = string(scope.requestBody.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
for _, processor := range scope.eventProcessors {
|
||||
id := event.EventID
|
||||
category := event.toCategory()
|
||||
spanCountBefore := event.GetSpanCount()
|
||||
event = processor(event, hint)
|
||||
if event == nil {
|
||||
debuglog.Printf("Event dropped by one of the Scope EventProcessors: %s\n", id)
|
||||
if client != nil {
|
||||
client.reportRecorder.RecordOne(report.ReasonEventProcessor, category)
|
||||
if category == ratelimit.CategoryTransaction {
|
||||
client.reportRecorder.Record(report.ReasonEventProcessor, ratelimit.CategorySpan, int64(spanCountBefore))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if droppedSpans := spanCountBefore - event.GetSpanCount(); droppedSpans > 0 {
|
||||
if client != nil {
|
||||
client.reportRecorder.Record(report.ReasonEventProcessor, ratelimit.CategorySpan, int64(droppedSpans))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
// cloneContext returns a new context with keys and values copied from the passed one.
|
||||
//
|
||||
// Note: a new Context (map) is returned, but the function does NOT do
|
||||
// a proper deep copy: if some context values are pointer types (e.g. maps),
|
||||
// they won't be properly copied.
|
||||
func cloneContext(c Context) Context {
|
||||
res := make(Context, len(c))
|
||||
for k, v := range c {
|
||||
res[k] = v
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (scope *Scope) populateAttrs(attrs map[string]attribute.Value) {
|
||||
if scope == nil {
|
||||
return
|
||||
}
|
||||
|
||||
scope.mu.RLock()
|
||||
defer scope.mu.RUnlock()
|
||||
|
||||
// Add user-related attributes
|
||||
if !scope.user.IsEmpty() {
|
||||
if scope.user.ID != "" {
|
||||
attrs["user.id"] = attribute.StringValue(scope.user.ID)
|
||||
}
|
||||
if scope.user.Name != "" {
|
||||
attrs["user.name"] = attribute.StringValue(scope.user.Name)
|
||||
}
|
||||
if scope.user.Email != "" {
|
||||
attrs["user.email"] = attribute.StringValue(scope.user.Email)
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range scope.attributes {
|
||||
attrs[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// hubFromContexts is a helper to return the first hub found in the given contexts.
|
||||
func hubFromContexts(ctxs ...context.Context) *Hub {
|
||||
for _, ctx := range ctxs {
|
||||
if ctx == nil {
|
||||
continue
|
||||
}
|
||||
if hub := GetHubFromContext(ctx); hub != nil {
|
||||
return hub
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveTrace resolves trace ID and span ID from the given scope and contexts.
|
||||
//
|
||||
// The resolution order follows a most-specific-to-least-specific pattern:
|
||||
// 1. If an external trace resolver was registered (eg. OTel), we prioritise trace context
|
||||
// information from that
|
||||
// 2. Check for span directly in contexts (SpanFromContext) - this is the most specific
|
||||
// source as it represents a span explicitly attached to the current operation's context
|
||||
// 3. Check scope's span - provides access to span set on the hub's scope
|
||||
// 4. Fall back to scope's propagation context trace ID
|
||||
//
|
||||
// This ordering ensures we always use the most contextually relevant tracing information.
|
||||
// For example, if a specific span is active for an operation, we use that span's trace/span IDs
|
||||
// rather than accidentally using a different span that might be set on the hub's scope.
|
||||
func resolveTrace(scope *Scope, client *Client, ctxs ...context.Context) (traceID TraceID, spanID SpanID) {
|
||||
var span *Span
|
||||
|
||||
for _, ctx := range ctxs {
|
||||
if ctx == nil {
|
||||
continue
|
||||
}
|
||||
if client != nil {
|
||||
if traceID, spanID, ok := client.externalTraceContextFromContext(ctx); ok {
|
||||
return traceID, spanID
|
||||
}
|
||||
}
|
||||
if span = SpanFromContext(ctx); span != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if scope != nil {
|
||||
scope.mu.RLock()
|
||||
if span == nil {
|
||||
span = scope.span
|
||||
}
|
||||
if span != nil {
|
||||
traceID = span.TraceID
|
||||
spanID = span.SpanID
|
||||
} else {
|
||||
traceID = scope.propagationContext.TraceID
|
||||
}
|
||||
scope.mu.RUnlock()
|
||||
}
|
||||
|
||||
return traceID, spanID
|
||||
}
|
||||
Reference in New Issue
Block a user