Skip to content

Commit a6c72b1

Browse files
committed
feat(core): Apply scope attributes to logs
1 parent 7971fc6 commit a6c72b1

File tree

8 files changed

+228
-13
lines changed

8 files changed

+228
-13
lines changed

dev-packages/browser-integration-tests/suites/public-api/metrics/simple/subject.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,10 @@ Sentry.startSpan({ name: 'test-span', op: 'test' }, () => {
99
Sentry.setUser({ id: 'user-123', email: '[email protected]', username: 'testuser' });
1010
Sentry.metrics.count('test.user.counter', 1, { attributes: { action: 'click' } });
1111

12+
Sentry.withScope(scope => {
13+
scope.setAttribute('scope_attribute_1', 1);
14+
scope.setAttributes({ scope_attribute_2: { value: 'test' }, scope_attribute_3: { value: 38, unit: 'gigabyte' } });
15+
Sentry.metrics.count('test.scope.attributes.counter', 1, { attributes: { action: 'click' } });
16+
})
17+
1218
Sentry.flush();

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

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page })
1717
expect(envelopeItems[0]).toEqual([
1818
{
1919
type: 'trace_metric',
20-
item_count: 5,
20+
item_count: 6,
2121
content_type: 'application/vnd.sentry.items.trace-metric+json',
2222
},
2323
{
@@ -98,6 +98,60 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page })
9898
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
9999
},
100100
},
101+
{
102+
timestamp: expect.any(Number),
103+
trace_id: expect.stringMatching(/^[\da-f]{32}$/),
104+
name: 'test.scope.attributes.counter',
105+
type: 'counter',
106+
value: 1,
107+
attributes: {
108+
action: {
109+
type: 'string',
110+
value: 'click',
111+
},
112+
scope_attribute_1: {
113+
type: 'integer',
114+
value: 1,
115+
},
116+
scope_attribute_2: {
117+
type: 'string',
118+
value: 'test',
119+
},
120+
scope_attribute_3: {
121+
type: 'integer',
122+
unit: 'gigabyte',
123+
value: 38,
124+
},
125+
'sentry.environment': {
126+
type: 'string',
127+
value: 'test',
128+
},
129+
'sentry.release': {
130+
type: 'string',
131+
value: '1.0.0',
132+
},
133+
'sentry.sdk.name': {
134+
type: 'string',
135+
value: 'sentry.javascript.browser',
136+
},
137+
'sentry.sdk.version': {
138+
type: 'string',
139+
value: '10.32.1',
140+
},
141+
'user.email': {
142+
type: 'string',
143+
144+
},
145+
'user.id': {
146+
type: 'string',
147+
value: 'user-123',
148+
},
149+
'user.name': {
150+
type: 'string',
151+
value: 'testuser',
152+
},
153+
},
154+
},
101155
],
102156
},
103157
]);

dev-packages/node-core-integration-tests/suites/public-api/metrics/scenario.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ async function run(): Promise<void> {
2525
Sentry.setUser({ id: 'user-123', email: '[email protected]', username: 'testuser' });
2626
Sentry.metrics.count('test.user.counter', 1, { attributes: { action: 'click' } });
2727

28+
Sentry.withScope(scope => {
29+
scope.setAttribute('scope_attribute_1', 1);
30+
scope.setAttributes({ scope_attribute_2: { value: 'test' }, scope_attribute_3: { value: 38, unit: 'gigabyte' } });
31+
Sentry.metrics.count('test.scope.attributes.counter', 1, { attributes: { action: 'click' } });
32+
});
33+
2834
await Sentry.flush();
2935
}
3036

dev-packages/node-core-integration-tests/suites/public-api/metrics/test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,60 @@ describe('metrics', () => {
8787
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
8888
},
8989
},
90+
{
91+
timestamp: expect.any(Number),
92+
trace_id: expect.stringMatching(/^[\da-f]{32}$/),
93+
name: 'test.scope.attributes.counter',
94+
type: 'counter',
95+
value: 1,
96+
attributes: {
97+
action: {
98+
type: 'string',
99+
value: 'click',
100+
},
101+
scope_attribute_1: {
102+
type: 'integer',
103+
value: 1,
104+
},
105+
scope_attribute_2: {
106+
type: 'string',
107+
value: 'test',
108+
},
109+
scope_attribute_3: {
110+
type: 'integer',
111+
unit: 'gigabyte',
112+
value: 38,
113+
},
114+
'sentry.environment': {
115+
type: 'string',
116+
value: 'test',
117+
},
118+
'sentry.release': {
119+
type: 'string',
120+
value: '1.0.0',
121+
},
122+
'sentry.sdk.name': {
123+
type: 'string',
124+
value: 'sentry.javascript.node-core',
125+
},
126+
'sentry.sdk.version': {
127+
type: 'string',
128+
value: '10.32.1',
129+
},
130+
'user.email': {
131+
type: 'string',
132+
133+
},
134+
'user.id': {
135+
type: 'string',
136+
value: 'user-123',
137+
},
138+
'user.name': {
139+
type: 'string',
140+
value: 'testuser',
141+
},
142+
},
143+
},
90144
],
91145
},
92146
})

dev-packages/node-integration-tests/suites/public-api/metrics/scenario.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ async function run(): Promise<void> {
2222
Sentry.setUser({ id: 'user-123', email: '[email protected]', username: 'testuser' });
2323
Sentry.metrics.count('test.user.counter', 1, { attributes: { action: 'click' } });
2424

25+
Sentry.withScope(scope => {
26+
scope.setAttribute('scope_attribute_1', 1);
27+
scope.setAttributes({ scope_attribute_2: { value: 'test' }, scope_attribute_3: { value: 38, unit: 'gigabyte' } });
28+
Sentry.metrics.count('test.scope.attributes.counter', 1, { attributes: { action: 'click' } });
29+
});
30+
2531
await Sentry.flush();
2632
}
2733

dev-packages/node-integration-tests/suites/public-api/metrics/test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,60 @@ describe('metrics', () => {
8686
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
8787
},
8888
},
89+
{
90+
timestamp: expect.any(Number),
91+
trace_id: expect.stringMatching(/^[\da-f]{32}$/),
92+
name: 'test.scope.attributes.counter',
93+
type: 'counter',
94+
value: 1,
95+
attributes: {
96+
action: {
97+
type: 'string',
98+
value: 'click',
99+
},
100+
scope_attribute_1: {
101+
type: 'integer',
102+
value: 1,
103+
},
104+
scope_attribute_2: {
105+
type: 'string',
106+
value: 'test',
107+
},
108+
scope_attribute_3: {
109+
type: 'integer',
110+
unit: 'gigabyte',
111+
value: 38,
112+
},
113+
'sentry.environment': {
114+
type: 'string',
115+
value: 'test',
116+
},
117+
'sentry.release': {
118+
type: 'string',
119+
value: '1.0.0',
120+
},
121+
'sentry.sdk.name': {
122+
type: 'string',
123+
value: 'sentry.javascript.node',
124+
},
125+
'sentry.sdk.version': {
126+
type: 'string',
127+
value: '10.32.1',
128+
},
129+
'user.email': {
130+
type: 'string',
131+
132+
},
133+
'user.id': {
134+
type: 'string',
135+
value: 'user-123',
136+
},
137+
'user.name': {
138+
type: 'string',
139+
value: 'testuser',
140+
},
141+
},
142+
},
89143
],
90144
},
91145
})

packages/core/src/metrics/internal.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { serializeAttributes } from '../attributes';
1+
import { type RawAttributes, serializeAttributes } from '../attributes';
22
import { getGlobalSingleton } from '../carrier';
33
import type { Client } from '../client';
44
import { getClient, getCurrentScope, getIsolationScope } from '../currentScopes';
55
import { DEBUG_BUILD } from '../debug-build';
66
import type { Scope } from '../scope';
77
import type { Integration } from '../types-hoist/integration';
88
import type { Metric, SerializedMetric } from '../types-hoist/metric';
9+
import type { User } from '../types-hoist/user';
910
import { debug } from '../utils/debug-logger';
1011
import { getCombinedScopeData } from '../utils/scopeData';
1112
import { _getSpanForScope } from '../utils/spanOnScope';
@@ -77,20 +78,17 @@ export interface InternalCaptureMetricOptions {
7778
/**
7879
* Enriches metric with all contextual attributes (user, SDK metadata, replay, etc.)
7980
*/
80-
function _enrichMetricAttributes(beforeMetric: Metric, client: Client, currentScope: Scope): Metric {
81+
function _enrichMetricAttributes(beforeMetric: Metric, client: Client, user: User): Metric {
8182
const { release, environment } = client.getOptions();
8283

8384
const processedMetricAttributes = {
8485
...beforeMetric.attributes,
8586
};
8687

8788
// Add user attributes
88-
const {
89-
user: { id, email, username },
90-
} = getCombinedScopeData(getIsolationScope(), currentScope);
91-
setMetricAttribute(processedMetricAttributes, 'user.id', id, false);
92-
setMetricAttribute(processedMetricAttributes, 'user.email', email, false);
93-
setMetricAttribute(processedMetricAttributes, 'user.name', username, false);
89+
setMetricAttribute(processedMetricAttributes, 'user.id', user.id, false);
90+
setMetricAttribute(processedMetricAttributes, 'user.email', user.email, false);
91+
setMetricAttribute(processedMetricAttributes, 'user.name', user.username, false);
9492

9593
// Add Sentry metadata
9694
setMetricAttribute(processedMetricAttributes, 'sentry.release', release);
@@ -125,7 +123,12 @@ function _enrichMetricAttributes(beforeMetric: Metric, client: Client, currentSc
125123
/**
126124
* Creates a serialized metric ready to be sent to Sentry.
127125
*/
128-
function _buildSerializedMetric(metric: Metric, client: Client, currentScope: Scope): SerializedMetric {
126+
function _buildSerializedMetric(
127+
metric: Metric,
128+
client: Client,
129+
currentScope: Scope,
130+
scopeAttributes: RawAttributes<Record<string, unknown>> | undefined,
131+
): SerializedMetric {
129132
// Get trace context
130133
const [, traceContext] = _getTraceInfoFromScope(client, currentScope);
131134
const span = _getSpanForScope(currentScope);
@@ -140,7 +143,10 @@ function _buildSerializedMetric(metric: Metric, client: Client, currentScope: Sc
140143
type: metric.type,
141144
unit: metric.unit,
142145
value: metric.value,
143-
attributes: serializeAttributes(metric.attributes, 'skip-undefined'),
146+
attributes: {
147+
...serializeAttributes(scopeAttributes),
148+
...serializeAttributes(metric.attributes, 'skip-undefined'),
149+
},
144150
};
145151
}
146152

@@ -174,7 +180,8 @@ export function _INTERNAL_captureMetric(beforeMetric: Metric, options?: Internal
174180
}
175181

176182
// Enrich metric with contextual attributes
177-
const enrichedMetric = _enrichMetricAttributes(beforeMetric, client, currentScope);
183+
const { user, attributes: scopeAttributes } = getCombinedScopeData(getIsolationScope(), currentScope);
184+
const enrichedMetric = _enrichMetricAttributes(beforeMetric, client, user);
178185

179186
client.emit('processMetric', enrichedMetric);
180187

@@ -188,7 +195,7 @@ export function _INTERNAL_captureMetric(beforeMetric: Metric, options?: Internal
188195
return;
189196
}
190197

191-
const serializedMetric = _buildSerializedMetric(processedMetric, client, currentScope);
198+
const serializedMetric = _buildSerializedMetric(processedMetric, client, currentScope, scopeAttributes);
192199

193200
DEBUG_BUILD && debug.log('[Metric]', serializedMetric);
194201

packages/core/test/lib/metrics/internal.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,34 @@ describe('_INTERNAL_captureMetric', () => {
171171
});
172172
});
173173

174+
it('includes scope attributes in metric attributes', () => {
175+
const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN });
176+
const client = new TestClient(options);
177+
const scope = new Scope();
178+
scope.setClient(client);
179+
scope.setAttribute('scope_attribute_1', 1);
180+
scope.setAttributes({ scope_attribute_2: { value: 'test' }, scope_attribute_3: { value: 38, unit: 'gigabyte' } });
181+
182+
_INTERNAL_captureMetric({ type: 'counter', name: 'test.metric', value: 1 }, { scope });
183+
184+
const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes;
185+
expect(metricAttributes).toEqual({
186+
scope_attribute_1: {
187+
value: 1,
188+
type: 'integer',
189+
},
190+
scope_attribute_2: {
191+
value: 'test',
192+
type: 'string',
193+
},
194+
scope_attribute_3: {
195+
value: 38,
196+
unit: 'gigabyte',
197+
type: 'integer',
198+
},
199+
});
200+
});
201+
174202
it('flushes metrics buffer when it reaches max size', () => {
175203
const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN });
176204
const client = new TestClient(options);

0 commit comments

Comments
 (0)