Skip to content

Commit 8da7295

Browse files
authored
feat(core): Apply scope attributes to logs (#18184)
Add support for scope attributes on logs. For now, only primitive attribute values are supported despite type declarations of `Scope.setAttribute(s)` also allowing array attribute values. The reason for this limited support is that Relay still discards array attribute values. Therefore, our serialization strategy for now is: - **As previously**: We continue to stringify non-primitive values for log/metric attributes - **New:** We apply only primitive scope attributes on logs/metrics and discard any non-primitive values - **Future:** We'll uniformly handle arrays (properly) in v11 (i.e. no longer stringify them), i.e. treat all attributes equally. ### Usage Example ```ts Sentry.getCurrenScope().setAttribute('user_is_admin', true); Sentry.logger.info(`user ${user.id} logged in`, { activeSince: 100 }); Sentry.logger.warn('unsupported version'); // `user_is_admin` attribute is applied to both logs ``` Some behavior notes: - Scope attributes are merged from all active scopes (current, isolation, global scopes) when the log is captured - Log attributes have precedence over scope attributes closes #18159
1 parent 968e529 commit 8da7295

File tree

17 files changed

+829
-532
lines changed

17 files changed

+829
-532
lines changed

.size-limit.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ module.exports = [
213213
import: createImport('init'),
214214
ignore: ['next/router', 'next/constants'],
215215
gzip: true,
216-
limit: '46 KB',
216+
limit: '46.5 KB',
217217
},
218218
// SvelteKit SDK (ESM)
219219
{

dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { expect } from '@playwright/test';
22
import type { LogEnvelope } from '@sentry/core';
33
import { sentryTest } from '../../../../utils/fixtures';
4-
import { getFirstSentryEnvelopeRequest, properFullEnvelopeRequestParser } from '../../../../utils/helpers';
4+
import {
5+
getFirstSentryEnvelopeRequest,
6+
properFullEnvelopeRequestParser,
7+
testingCdnBundle,
8+
} from '../../../../utils/helpers';
59

610
sentryTest('should capture console object calls', async ({ getLocalTestUrl, page }) => {
7-
const bundle = process.env.PW_BUNDLE || '';
811
// Only run this for npm package exports
9-
if (bundle.startsWith('bundle') || bundle.startsWith('loader')) {
10-
sentryTest.skip();
11-
}
12+
sentryTest.skip(testingCdnBundle());
1213

1314
const url = await getLocalTestUrl({ testDir: __dirname });
1415

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// only log attribute
2+
Sentry.logger.info('log_before_any_scope', { log_attr: 'log_attr_1' });
3+
4+
Sentry.getGlobalScope().setAttributes({ global_scope_attr: true });
5+
6+
// this attribute will not be sent for now
7+
Sentry.getGlobalScope().setAttribute('array_attr', [1, 2, 3]);
8+
9+
// global scope, log attribute
10+
Sentry.logger.info('log_after_global_scope', { log_attr: 'log_attr_2' });
11+
12+
Sentry.withIsolationScope(isolationScope => {
13+
isolationScope.setAttribute('isolation_scope_1_attr', { value: 100, unit: 'millisecond' });
14+
15+
// global scope, isolation scope, log attribute
16+
Sentry.logger.info('log_with_isolation_scope', { log_attr: 'log_attr_3' });
17+
18+
Sentry.withScope(scope => {
19+
scope.setAttributes({ scope_attr: { value: 200, unit: 'millisecond' } });
20+
21+
// global scope, isolation scope, current scope attribute, log attribute
22+
Sentry.logger.info('log_with_scope', { log_attr: 'log_attr_4' });
23+
});
24+
25+
Sentry.withScope(scope2 => {
26+
scope2.setAttribute('scope_2_attr', { value: 300, unit: 'millisecond' });
27+
28+
// global scope, isolation scope, current scope attribute, log attribute
29+
Sentry.logger.info('log_with_scope_2', { log_attr: 'log_attr_5' });
30+
});
31+
});
32+
33+
Sentry.flush();
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { expect } from '@playwright/test';
2+
import type { LogEnvelope } from '@sentry/core';
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import {
5+
getFirstSentryEnvelopeRequest,
6+
properFullEnvelopeRequestParser,
7+
testingCdnBundle,
8+
} from '../../../../utils/helpers';
9+
10+
sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page }) => {
11+
sentryTest.skip(testingCdnBundle());
12+
13+
const url = await getLocalTestUrl({ testDir: __dirname });
14+
15+
const event = await getFirstSentryEnvelopeRequest<LogEnvelope>(page, url, properFullEnvelopeRequestParser);
16+
const envelopeItems = event[1];
17+
18+
expect(envelopeItems[0]).toEqual([
19+
{
20+
type: 'log',
21+
item_count: 5,
22+
content_type: 'application/vnd.sentry.items.log+json',
23+
},
24+
{
25+
items: [
26+
{
27+
timestamp: expect.any(Number),
28+
level: 'info',
29+
body: 'log_before_any_scope',
30+
severity_number: 9,
31+
trace_id: expect.any(String),
32+
attributes: {
33+
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
34+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
35+
log_attr: { value: 'log_attr_1', type: 'string' },
36+
},
37+
},
38+
{
39+
timestamp: expect.any(Number),
40+
level: 'info',
41+
body: 'log_after_global_scope',
42+
severity_number: 9,
43+
trace_id: expect.any(String),
44+
attributes: {
45+
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
46+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
47+
global_scope_attr: { value: true, type: 'boolean' },
48+
log_attr: { value: 'log_attr_2', type: 'string' },
49+
},
50+
},
51+
{
52+
timestamp: expect.any(Number),
53+
level: 'info',
54+
body: 'log_with_isolation_scope',
55+
severity_number: 9,
56+
trace_id: expect.any(String),
57+
attributes: {
58+
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
59+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
60+
global_scope_attr: { value: true, type: 'boolean' },
61+
isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' },
62+
log_attr: { value: 'log_attr_3', type: 'string' },
63+
},
64+
},
65+
{
66+
timestamp: expect.any(Number),
67+
level: 'info',
68+
body: 'log_with_scope',
69+
severity_number: 9,
70+
trace_id: expect.any(String),
71+
attributes: {
72+
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
73+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
74+
global_scope_attr: { value: true, type: 'boolean' },
75+
isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' },
76+
scope_attr: { value: 200, unit: 'millisecond', type: 'integer' },
77+
log_attr: { value: 'log_attr_4', type: 'string' },
78+
},
79+
},
80+
{
81+
timestamp: expect.any(Number),
82+
level: 'info',
83+
body: 'log_with_scope_2',
84+
severity_number: 9,
85+
trace_id: expect.any(String),
86+
attributes: {
87+
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
88+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
89+
global_scope_attr: { value: true, type: 'boolean' },
90+
isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' },
91+
scope_2_attr: { value: 300, unit: 'millisecond', type: 'integer' },
92+
log_attr: { value: 'log_attr_5', type: 'string' },
93+
},
94+
},
95+
],
96+
},
97+
]);
98+
});

dev-packages/browser-integration-tests/suites/public-api/logger/simple/test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { expect } from '@playwright/test';
22
import type { LogEnvelope } from '@sentry/core';
33
import { sentryTest } from '../../../../utils/fixtures';
4-
import { getFirstSentryEnvelopeRequest, properFullEnvelopeRequestParser } from '../../../../utils/helpers';
4+
import {
5+
getFirstSentryEnvelopeRequest,
6+
properFullEnvelopeRequestParser,
7+
testingCdnBundle,
8+
} from '../../../../utils/helpers';
59

610
sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page }) => {
7-
const bundle = process.env.PW_BUNDLE || '';
811
// Only run this for npm package exports
9-
if (bundle.startsWith('bundle') || bundle.startsWith('loader')) {
10-
sentryTest.skip();
11-
}
12+
sentryTest.skip(testingCdnBundle());
1213

1314
const url = await getLocalTestUrl({ testDir: __dirname });
1415

dev-packages/browser-integration-tests/utils/helpers.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,14 @@ export function shouldSkipTracingTest(): boolean {
314314
return bundle != null && !bundle.includes('tracing') && !bundle.includes('esm') && !bundle.includes('cjs');
315315
}
316316

317+
/**
318+
* @returns `true` if we are testing a CDN bundle
319+
*/
320+
export function testingCdnBundle(): boolean {
321+
const bundle = process.env.PW_BUNDLE;
322+
return bundle != null && (bundle.startsWith('bundle') || bundle.startsWith('loader'));
323+
}
324+
317325
/**
318326
* Today we always run feedback tests, but this can be used to guard this if we ever need to.
319327
*/

0 commit comments

Comments
 (0)