Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions dynamic_sampling_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,8 @@ func (d DynamicSamplingContext) String() string {
return baggage.String()
}

// DynamicSamplingContextFromScope Constructs a new DynamicSamplingContext using a scope and client. Accessing
// fields on the scope are not thread safe, and this function should only be
// called within scope methods.
// DynamicSamplingContextFromScope constructs a new DynamicSamplingContext
// using a scope and client.
func DynamicSamplingContextFromScope(scope *Scope, client Client) DynamicSamplingContext {
entries := map[string]string{}
client = normalizeClient(client)
Expand All @@ -131,7 +130,15 @@ func DynamicSamplingContextFromScope(scope *Scope, client Client) DynamicSamplin
}
}

propagationContext := scope.propagationContext
return dynamicSamplingContextFromPropagationContext(scope.propagationContextSnapshot(), client)
}

func dynamicSamplingContextFromPropagationContext(propagationContext PropagationContext, client Client) DynamicSamplingContext {
entries := map[string]string{}
client = normalizeClient(client)
if !client.IsEnabled() {
return DynamicSamplingContext{Entries: entries, Frozen: false}
}

if traceID := propagationContext.TraceID.String(); traceID != "" {
entries["trace_id"] = traceID
Expand Down
163 changes: 112 additions & 51 deletions scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,84 +342,148 @@ func (scope *Scope) AddEventProcessor(processor EventProcessor) {
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
client = normalizeClient(client)
type scopeSnapshot struct {
scopeData
eventProcessors []EventProcessor
}

scope.mu.RLock()
defer scope.mu.RUnlock()
// snapshotScopes copies and merges scopes from least to most specific.
func snapshotScopes(client Client, scopes ...*Scope) scopeSnapshot {
var snapshot scopeSnapshot
for _, scope := range scopes {
if scope == nil {
continue
}
scope.mu.RLock()
snapshot.mergeLocked(scope)
scope.mu.RUnlock()
}

if len(scope.breadcrumbs) > 0 {
event.Breadcrumbs = append(event.Breadcrumbs, scope.breadcrumbs...)
limit := normalizeClient(client).clientOptions().MaxBreadcrumbs
switch {
case limit < 0:
snapshot.breadcrumbs = nil
case limit == 0:
limit = defaultMaxBreadcrumbs
}
if limit >= 0 && len(snapshot.breadcrumbs) > limit {
snapshot.breadcrumbs = snapshot.breadcrumbs[len(snapshot.breadcrumbs)-limit:]
}

return snapshot
}

// mergeLocked copies scope data into snapshot. The caller must hold scope.mu
// for reading. It does not invoke user callbacks.
func (snapshot *scopeSnapshot) mergeLocked(scope *Scope) {
snapshot.breadcrumbs = append(snapshot.breadcrumbs, scope.breadcrumbs...)
snapshot.attachments = append(snapshot.attachments, scope.attachments...)
snapshot.eventProcessors = append(snapshot.eventProcessors, scope.eventProcessors...)

if snapshot.attributes == nil {
snapshot.attributes = make(map[string]attribute.Value, len(scope.attributes))
}
for key, value := range scope.attributes {
snapshot.attributes[key] = value
}
if snapshot.tags == nil {
snapshot.tags = make(map[string]string, len(scope.tags))
}
for key, value := range scope.tags {
snapshot.tags[key] = value
}
if snapshot.contexts == nil {
snapshot.contexts = make(map[string]Context, len(scope.contexts))
}
for key, value := range scope.contexts {
snapshot.contexts[key] = value
}

if !scope.user.IsEmpty() {
snapshot.user = scope.user
}
if len(scope.fingerprint) > 0 {
snapshot.fingerprint = append(snapshot.fingerprint[:0], scope.fingerprint...)
}
if scope.level != "" {
snapshot.level = scope.level
}
if scope.request != nil {
snapshot.request = scope.request
snapshot.requestBody = scope.requestBody
}

if len(scope.attachments) > 0 {
event.Attachments = append(event.Attachments, scope.attachments...)
snapshot.propagationContext = scope.propagationContext
if scope.span != nil {
snapshot.span = scope.span
}
}

// 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 {
return snapshotScopes(client, scope).applyToEvent(event, hint, client)
}

func (snapshot scopeSnapshot) applyToEvent(event *Event, hint *EventHint, client Client) *Event { //nolint:gocyclo
client = normalizeClient(client)

if len(scope.tags) > 0 {
if len(snapshot.breadcrumbs) > 0 {
event.Breadcrumbs = append(event.Breadcrumbs, snapshot.breadcrumbs...)
}
if len(snapshot.attachments) > 0 {
event.Attachments = append(event.Attachments, snapshot.attachments...)
}
if len(snapshot.tags) > 0 {
if event.Tags == nil {
event.Tags = make(map[string]string, len(scope.tags))
event.Tags = make(map[string]string, len(snapshot.tags))
}

for key, value := range scope.tags {
event.Tags[key] = value
for key, value := range snapshot.tags {
if _, ok := event.Tags[key]; !ok {
event.Tags[key] = value
}
}
}

if len(scope.contexts) > 0 {
if len(snapshot.contexts) > 0 {
if event.Contexts == nil {
event.Contexts = make(map[string]Context)
}

for key, value := range scope.contexts {
for key, value := range snapshot.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 snapshot.span != nil {
if _, ok := event.Contexts["trace"]; !ok {
event.Contexts["trace"] = scope.span.traceContext().Map()
event.Contexts["trace"] = snapshot.span.traceContext().Map()
}

transaction := scope.span.GetTransaction()
if transaction != nil {
if transaction := snapshot.span.GetTransaction(); transaction != nil {
event.sdkMetaData.dsc = DynamicSamplingContextFromTransaction(transaction)
}
} else {
event.Contexts["trace"] = scope.propagationContext.Map()

dsc := scope.propagationContext.DynamicSamplingContext
if _, ok := event.Contexts["trace"]; !ok {
event.Contexts["trace"] = snapshot.propagationContext.Map()
}
dsc := snapshot.propagationContext.DynamicSamplingContext
if !dsc.HasEntries() {
dsc = DynamicSamplingContextFromScope(scope, client)
dsc = dynamicSamplingContextFromPropagationContext(snapshot.propagationContext, 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.
var ctx context.Context
if hint != nil {
ctx = hint.Context
}
if ctx == nil && scope.request != nil {
ctx = scope.request.Context()
if ctx == nil && snapshot.request != nil {
ctx = snapshot.request.Context()
}
if traceID, spanID, ok := client.externalTraceContextFromContext(ctx); event.Type != transactionType && ok {
traceCtx := event.Contexts["trace"]
Expand All @@ -428,19 +492,16 @@ func (scope *Scope) ApplyToEvent(event *Event, hint *EventHint, client Client) *
}

if event.User.IsEmpty() {
event.User = scope.user
event.User = snapshot.user
}

if len(event.Fingerprint) == 0 {
event.Fingerprint = append(event.Fingerprint, scope.fingerprint...)
event.Fingerprint = append(event.Fingerprint, snapshot.fingerprint...)
}

if scope.level != "" {
event.Level = scope.level
if event.Level == "" {
event.Level = snapshot.level
Comment thread
cursor[bot] marked this conversation as resolved.
}

if event.Request == nil && scope.request != nil {
event.Request = newRequest(scope.request, client)
if event.Request == nil && snapshot.request != nil {
event.Request = newRequest(snapshot.request, 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
Expand All @@ -451,12 +512,12 @@ func (scope *Scope) ApplyToEvent(event *Event, hint *EventHint, client Client) *
// Users can still send more data along their events if they want to,
// for example using Event.Contexts.
dc := client.GetDataCollection()
if scope.requestBody != nil && !scope.requestBody.Overflow() && dc.CollectHTTPBody(BodyIncomingRequest) {
event.Request.Data = dc.FilterHTTPBody(scope.requestBody.Bytes(), scope.request.Header.Get("Content-Type"))
if snapshot.requestBody != nil && !snapshot.requestBody.Overflow() && dc.CollectHTTPBody(BodyIncomingRequest) {
event.Request.Data = dc.FilterHTTPBody(snapshot.requestBody.Bytes(), snapshot.request.Header.Get("Content-Type"))
}
}

for _, processor := range scope.eventProcessors {
for _, processor := range snapshot.eventProcessors {
id := event.EventID
category := event.toCategory()
spanCountBefore := event.GetSpanCount()
Expand Down
19 changes: 13 additions & 6 deletions scope_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ func fillScopeWithData(scope *Scope) *Scope {
},
}
scope.user = User{ID: "1337"}
scope.tags = map[string]string{"scopeTagKey": "scopeTagValue"}
scope.tags = map[string]string{
"scopeTagKey": "scopeTagValue",
"sharedTagKey": "scopeTagValue",
}
scope.contexts = map[string]Context{
"scopeContextsKey": {"scopeContextKey": "scopeContextValue"},
sharedContextsKey: {"scopeContextKey": "scopeContextValue"},
Expand All @@ -46,7 +49,10 @@ func fillEventWithData(event *Event) *Event {
},
}
event.User = User{ID: "42"}
event.Tags = map[string]string{"eventTagKey": "eventTagValue"}
event.Tags = map[string]string{
"eventTagKey": "eventTagValue",
"sharedTagKey": "eventTagValue",
}
event.Contexts = map[string]Context{
"eventContextsKey": {"eventContextKey": "eventContextValue"},
sharedContextsKey: {"eventContextKey": "eventContextKey"},
Expand Down Expand Up @@ -610,10 +616,11 @@ func TestApplyToEventWithCorrectScopeAndEvent(t *testing.T) {

assertEqual(t, 2, len(processedEvent.Breadcrumbs), "should merge breadcrumbs")
assertEqual(t, 2, len(processedEvent.Attachments), "should merge attachments")
assertEqual(t, 2, len(processedEvent.Tags), "should merge tags")
assertEqual(t, 3, len(processedEvent.Tags), "should merge tags")
assertEqual(t, "eventTagValue", processedEvent.Tags["sharedTagKey"], "should not override event tags")
assertEqual(t, 4, len(processedEvent.Contexts), "should merge contexts")
assertEqual(t, event.Contexts[sharedContextsKey], processedEvent.Contexts[sharedContextsKey], "should not override event trace context")
assertEqual(t, LevelDebug, processedEvent.Level, "should use event level if set")
assertEqual(t, LevelInfo, processedEvent.Level, "should use event level if set")
assertEqual(t, event.User, processedEvent.User, "should use event user if one exists")
assertEqual(t, event.Request, processedEvent.Request, "should use event request if one exists")
assertEqual(t, event.Fingerprint, processedEvent.Fingerprint, "should use event fingerprints if they exist")
Expand All @@ -629,7 +636,7 @@ func TestApplyToEventUsingEmptyScope(t *testing.T) {
processedEvent := scope.ApplyToEvent(event, nil, noopClient{})
assertEqual(t, len(processedEvent.Breadcrumbs), 1, "should use event breadcrumbs")
assertEqual(t, len(processedEvent.Attachments), 1, "should use event attachments")
assertEqual(t, len(processedEvent.Tags), 1, "should use event tags")
assertEqual(t, len(processedEvent.Tags), 2, "should use event tags")
assertEqual(t, len(processedEvent.Contexts), 3, "should use event contexts")
assertEqual(t, processedEvent.User, event.User, "should use event user")
assertEqual(t, processedEvent.Fingerprint, event.Fingerprint, "should use event fingerprint")
Expand All @@ -644,7 +651,7 @@ func TestApplyToEventUsingEmptyEvent(t *testing.T) {
processedEvent := scope.ApplyToEvent(event, nil, noopClient{})
assertEqual(t, len(processedEvent.Breadcrumbs), 1, "should use scope breadcrumbs")
assertEqual(t, len(processedEvent.Attachments), 1, "should use scope attachments")
assertEqual(t, len(processedEvent.Tags), 1, "should use scope tags")
assertEqual(t, len(processedEvent.Tags), 2, "should use scope tags")
assertEqual(t, len(processedEvent.Contexts), 3, "should use scope contexts")
assertEqual(t, processedEvent.User, scope.user, "should use scope user")
assertEqual(t, processedEvent.Fingerprint, scope.fingerprint, "should use scope fingerprint")
Expand Down
Loading