-
Notifications
You must be signed in to change notification settings - Fork 260
feat: add scope context API #1371
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -15,23 +15,22 @@ import ( | |
| "github.com/getsentry/sentry-go/report" | ||
| ) | ||
|
|
||
| // Scope holds contextual data for the current scope. | ||
| // Scope holds contextual data for an operation. | ||
| // | ||
| // 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 is an object that can be cloned efficiently and stores data that is | ||
| // locally relevant to an event. It also holds the client and event processor in | ||
| // which the scope data should be applied to. | ||
| // | ||
| // 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. | ||
| // Clearing or cloning the scope only affects the underlying data. To set a new | ||
| // client or event processor, SetClient or AddEventProcessor should be used. | ||
| type Scope struct { | ||
| mu sync.RWMutex | ||
| mu sync.RWMutex | ||
| // clientOverride is an explicit client binding set with SetClient. Having | ||
| // no override defaults to the global scope client. | ||
| clientOverride *Client | ||
| // eventProcessors are retained by Clear and inherited by Clone. | ||
| eventProcessors []EventProcessor | ||
| lastEventID EventID | ||
|
|
||
| // scopeData keeps track of all scope specific data | ||
| scopeData | ||
|
|
@@ -58,14 +57,21 @@ type scopeData struct { | |
| } | ||
|
|
||
| propagationContext PropagationContext | ||
| span *Span | ||
| span *Span // TODO: this should be removed when the span API is introduced. Currently kept for compatibility. | ||
| } | ||
|
|
||
| // NewScope creates a new Scope. | ||
| func NewScope() *Scope { | ||
| return &Scope{scopeData: newScopeData()} | ||
| } | ||
|
|
||
| // newScopeWithClient creates a Scope with an explicit client override. | ||
| func newScopeWithClient(client *Client) *Scope { | ||
| scope := NewScope() | ||
| scope.SetClient(client) | ||
| return scope | ||
| } | ||
|
|
||
| func newScopeData() scopeData { | ||
| return scopeData{ | ||
| attributes: make(map[string]attribute.Value), | ||
|
|
@@ -94,6 +100,54 @@ func (scope *Scope) AddBreadcrumb(breadcrumb *Breadcrumb, limit int) { | |
| } | ||
| } | ||
|
|
||
| // SetClient sets an explicit client override on the scope. Passing nil clears | ||
| // the override so client resolution falls back to GlobalScope. | ||
| func (scope *Scope) SetClient(client *Client) { | ||
| scope.mu.Lock() | ||
| defer scope.mu.Unlock() | ||
|
|
||
| scope.clientOverride = normalizeClient(client) | ||
| } | ||
|
Comment on lines
+105
to
+110
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. Bug: The Suggested FixEither update the documentation to reflect the actual behavior, change the implementation to store Prompt for AI Agent |
||
|
|
||
| func (scope *Scope) clientOverrideSnapshot() *Client { | ||
| scope.mu.RLock() | ||
| defer scope.mu.RUnlock() | ||
|
|
||
| return scope.clientOverride | ||
| } | ||
|
|
||
| // Client returns the first enabled client in the scope chain. | ||
| func (scope *Scope) Client() *Client { | ||
| if scope != nil { | ||
| if client := normalizeClient(scope.clientOverrideSnapshot()); client.IsEnabled() { | ||
| return client | ||
| } | ||
| } | ||
|
|
||
| global := GlobalScope() | ||
| if scope != global { | ||
| if client := normalizeClient(global.clientOverrideSnapshot()); client.IsEnabled() { | ||
| return client | ||
| } | ||
| } | ||
| return NewNoopClient() | ||
| } | ||
|
|
||
| func (scope *Scope) setLastEventID(id EventID) { | ||
| scope.mu.Lock() | ||
| defer scope.mu.Unlock() | ||
|
|
||
| scope.lastEventID = id | ||
| } | ||
|
|
||
| // LastEventID returns the last event ID associated with this scope. | ||
| func (scope *Scope) LastEventID() EventID { | ||
| scope.mu.RLock() | ||
| defer scope.mu.RUnlock() | ||
|
|
||
| return scope.lastEventID | ||
| } | ||
|
|
||
| // ClearBreadcrumbs clears all breadcrumbs from the current scope. | ||
| func (scope *Scope) ClearBreadcrumbs() { | ||
| scope.mu.Lock() | ||
|
|
@@ -294,6 +348,8 @@ func (scope *Scope) Clone() *Scope { | |
| return &Scope{ | ||
| scopeData: data.clone(), | ||
| eventProcessors: scope.eventProcessors[:len(scope.eventProcessors):len(scope.eventProcessors)], | ||
| clientOverride: scope.clientOverride, | ||
| lastEventID: scope.lastEventID, | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| package sentry | ||
|
|
||
| import "context" | ||
|
|
||
| type scopeContextKey struct{} | ||
|
|
||
| // globalScope is the process-wide global scope. | ||
| var globalScope = newScopeWithClient(NewNoopClient()) | ||
|
|
||
| // GlobalScope returns the process-wide global scope. | ||
| func GlobalScope() *Scope { | ||
| return globalScope | ||
|
sentry[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| // ScopeFromContext returns the scope carried by ctx, or nil when ctx | ||
| // does not carry one. | ||
| func ScopeFromContext(ctx context.Context) *Scope { | ||
| if ctx == nil { | ||
| return nil | ||
| } | ||
| scope, _ := ctx.Value(scopeContextKey{}).(*Scope) | ||
| return scope | ||
| } | ||
|
|
||
| func contextWithScope(ctx context.Context, scope *Scope) context.Context { | ||
| return context.WithValue(ctx, scopeContextKey{}, scope) | ||
| } | ||
|
|
||
| // WithIsolation returns a derived context and an independent scope. | ||
| // It clones a carried scope or creates an empty scope when ctx does | ||
| // not carry one. | ||
| func WithIsolation(ctx context.Context) (context.Context, *Scope) { | ||
| parent := ScopeFromContext(ctx) | ||
| var scope *Scope | ||
| if parent == nil { | ||
| scope = NewScope() | ||
| } else { | ||
| scope = parent.Clone() | ||
| scope.SetPropagationContext(NewPropagationContext()) | ||
| scope.SetSpan(nil) | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| } | ||
| return contextWithScope(ctx, scope), scope | ||
| } | ||
|
|
||
| // WithScopeContext invokes fn with a derived context carrying a cloned scope. | ||
| func WithScopeContext(ctx context.Context, fn func(context.Context, *Scope)) { // TODO: should remove WithScope when hub is removed. | ||
| if fn == nil { | ||
| return | ||
| } | ||
|
|
||
| parent := ScopeFromContext(ctx) | ||
| if parent == nil { | ||
| parent = NewScope() | ||
| } | ||
| scope := parent.Clone() | ||
| fn(contextWithScope(ctx, scope), scope) | ||
| } | ||
|
giortzisg marked this conversation as resolved.
|
||
|
|
||
| // GetClient returns the first enabled client for ctx. | ||
| func GetClient(ctx context.Context) *Client { | ||
| if scope := ScopeFromContext(ctx); scope != nil { | ||
| return scope.Client() | ||
| } | ||
| return GlobalScope().Client() | ||
| } | ||
|
cursor[bot] marked this conversation as resolved.
|
||
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.
l: I find this paragraph to be slightly unclear. What is meant by the "underlying data" in this context?
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.
It's everything under
scopeData, which is essentially all the data set by the user. Not really sure how to call this.