Skip to content

Commit b327157

Browse files
authored
fix(core): Fix scope capturing via captureContext function (#10735) (#10737)
Backport to v7. In #9801, we introduced a regression that if you pass a function as `captureContext` to capture methods, the returned scope is not used. The cause for this was a confusion on my end, based on the slightly weird way this works in `scope.update(fn)` - we don't actually merge this or update the scope based on the return value of `fn`, but `fn` receives the `scope` as argument, does nothing with the return type of `fn` and just returns it - which we didn't use, because I assumed that `scope.update` would actually return the scope (also, the return type of it is `this` which is not correct there). This PR changes this so that the returned scope of `fn` is actually merged with the scope, same as if you'd pass a `scope` directly - so this is fundamentally the same now: ```js const otherScope = new Scope(); scope.update(otherScope); scope.update(() => otherScope); ``` (which before would have had vastly different outcomes!) I added a bunch of tests to verify how this works/should work. Fixes #10686
1 parent 0cc946d commit b327157

File tree

3 files changed

+551
-35
lines changed

3 files changed

+551
-35
lines changed

packages/core/src/scope.ts

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -378,50 +378,48 @@ export class Scope implements ScopeInterface {
378378
return this;
379379
}
380380

381-
if (typeof captureContext === 'function') {
382-
const updatedScope = (captureContext as <T>(scope: T) => T)(this);
383-
return updatedScope instanceof Scope ? updatedScope : this;
384-
}
381+
const scopeToMerge = typeof captureContext === 'function' ? captureContext(this) : captureContext;
382+
383+
if (scopeToMerge instanceof Scope) {
384+
const scopeData = scopeToMerge.getScopeData();
385385

386-
if (captureContext instanceof Scope) {
387-
this._tags = { ...this._tags, ...captureContext._tags };
388-
this._extra = { ...this._extra, ...captureContext._extra };
389-
this._contexts = { ...this._contexts, ...captureContext._contexts };
390-
if (captureContext._user && Object.keys(captureContext._user).length) {
391-
this._user = captureContext._user;
386+
this._tags = { ...this._tags, ...scopeData.tags };
387+
this._extra = { ...this._extra, ...scopeData.extra };
388+
this._contexts = { ...this._contexts, ...scopeData.contexts };
389+
if (scopeData.user && Object.keys(scopeData.user).length) {
390+
this._user = scopeData.user;
392391
}
393-
if (captureContext._level) {
394-
this._level = captureContext._level;
392+
if (scopeData.level) {
393+
this._level = scopeData.level;
395394
}
396-
if (captureContext._fingerprint) {
397-
this._fingerprint = captureContext._fingerprint;
395+
if (scopeData.fingerprint.length) {
396+
this._fingerprint = scopeData.fingerprint;
398397
}
399-
if (captureContext._requestSession) {
400-
this._requestSession = captureContext._requestSession;
398+
if (scopeToMerge.getRequestSession()) {
399+
this._requestSession = scopeToMerge.getRequestSession();
401400
}
402-
if (captureContext._propagationContext) {
403-
this._propagationContext = captureContext._propagationContext;
401+
if (scopeData.propagationContext) {
402+
this._propagationContext = scopeData.propagationContext;
404403
}
405-
} else if (isPlainObject(captureContext)) {
406-
// eslint-disable-next-line no-param-reassign
407-
captureContext = captureContext as ScopeContext;
408-
this._tags = { ...this._tags, ...captureContext.tags };
409-
this._extra = { ...this._extra, ...captureContext.extra };
410-
this._contexts = { ...this._contexts, ...captureContext.contexts };
411-
if (captureContext.user) {
412-
this._user = captureContext.user;
404+
} else if (isPlainObject(scopeToMerge)) {
405+
const scopeContext = captureContext as ScopeContext;
406+
this._tags = { ...this._tags, ...scopeContext.tags };
407+
this._extra = { ...this._extra, ...scopeContext.extra };
408+
this._contexts = { ...this._contexts, ...scopeContext.contexts };
409+
if (scopeContext.user) {
410+
this._user = scopeContext.user;
413411
}
414-
if (captureContext.level) {
415-
this._level = captureContext.level;
412+
if (scopeContext.level) {
413+
this._level = scopeContext.level;
416414
}
417-
if (captureContext.fingerprint) {
418-
this._fingerprint = captureContext.fingerprint;
415+
if (scopeContext.fingerprint) {
416+
this._fingerprint = scopeContext.fingerprint;
419417
}
420-
if (captureContext.requestSession) {
421-
this._requestSession = captureContext.requestSession;
418+
if (scopeContext.requestSession) {
419+
this._requestSession = scopeContext.requestSession;
422420
}
423-
if (captureContext.propagationContext) {
424-
this._propagationContext = captureContext.propagationContext;
421+
if (scopeContext.propagationContext) {
422+
this._propagationContext = scopeContext.propagationContext;
425423
}
426424
}
427425

packages/core/test/lib/prepareEvent.test.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,4 +379,130 @@ describe('prepareEvent', () => {
379379
},
380380
});
381381
});
382+
383+
describe('captureContext', () => {
384+
it('works with scope & captureContext=POJO', async () => {
385+
const scope = new Scope();
386+
scope.setTags({
387+
initial: 'aa',
388+
foo: 'foo',
389+
});
390+
391+
const event = { message: 'foo' };
392+
393+
const options = {} as ClientOptions;
394+
const client = {
395+
getEventProcessors() {
396+
return [] as EventProcessor[];
397+
},
398+
} as Client;
399+
400+
const processedEvent = await prepareEvent(
401+
options,
402+
event,
403+
{
404+
captureContext: { tags: { foo: 'bar' } },
405+
integrations: [],
406+
},
407+
scope,
408+
client,
409+
);
410+
411+
expect(processedEvent).toEqual({
412+
timestamp: expect.any(Number),
413+
event_id: expect.any(String),
414+
environment: 'production',
415+
message: 'foo',
416+
sdkProcessingMetadata: {},
417+
tags: { initial: 'aa', foo: 'bar' },
418+
});
419+
});
420+
421+
it('works with scope & captureContext=scope instance', async () => {
422+
const scope = new Scope();
423+
scope.setTags({
424+
initial: 'aa',
425+
foo: 'foo',
426+
});
427+
428+
const event = { message: 'foo' };
429+
430+
const options = {} as ClientOptions;
431+
const client = {
432+
getEventProcessors() {
433+
return [] as EventProcessor[];
434+
},
435+
} as Client;
436+
437+
const captureContext = new Scope();
438+
captureContext.setTags({ foo: 'bar' });
439+
440+
const processedEvent = await prepareEvent(
441+
options,
442+
event,
443+
{
444+
captureContext,
445+
integrations: [],
446+
},
447+
scope,
448+
client,
449+
);
450+
451+
expect(processedEvent).toEqual({
452+
timestamp: expect.any(Number),
453+
event_id: expect.any(String),
454+
environment: 'production',
455+
message: 'foo',
456+
sdkProcessingMetadata: {},
457+
tags: { initial: 'aa', foo: 'bar' },
458+
});
459+
});
460+
461+
it('works with scope & captureContext=function', async () => {
462+
const scope = new Scope();
463+
scope.setTags({
464+
initial: 'aa',
465+
foo: 'foo',
466+
});
467+
468+
const event = { message: 'foo' };
469+
470+
const options = {} as ClientOptions;
471+
const client = {
472+
getEventProcessors() {
473+
return [] as EventProcessor[];
474+
},
475+
} as Client;
476+
477+
const captureContextScope = new Scope();
478+
captureContextScope.setTags({ foo: 'bar' });
479+
480+
const captureContext = jest.fn(passedScope => {
481+
expect(passedScope).toEqual(scope);
482+
return captureContextScope;
483+
});
484+
485+
const processedEvent = await prepareEvent(
486+
options,
487+
event,
488+
{
489+
captureContext,
490+
integrations: [],
491+
},
492+
scope,
493+
client,
494+
);
495+
496+
expect(captureContext).toHaveBeenCalledTimes(1);
497+
498+
expect(processedEvent).toEqual({
499+
timestamp: expect.any(Number),
500+
event_id: expect.any(String),
501+
environment: 'production',
502+
message: 'foo',
503+
sdkProcessingMetadata: {},
504+
tags: { initial: 'aa', foo: 'bar' },
505+
});
506+
});
507+
});
382508
});

0 commit comments

Comments
 (0)