Skip to content

Commit f4c6c54

Browse files
k-fishchargome
andauthored
feat(tracemetrics): Add trace metrics behind an experiments flag (#17883)
### Summary This allows the js sdk to send in new trace metric protocol items, although this code is experimental since the schema may still change. Most of this has been copied from logs so some parts may need to be modified / removed later (eg. api, buffer) but this should allow us to start on UI work by sending in larger amounts of data from sentry js app to test grouping / aggregations etc. Closes LOGS-366 --------- Co-authored-by: Charly Gomez <[email protected]>
1 parent 0837acc commit f4c6c54

File tree

32 files changed

+2799
-7
lines changed

32 files changed

+2799
-7
lines changed

.size-limit.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ module.exports = [
9696
path: 'packages/browser/build/npm/esm/index.js',
9797
import: createImport('init', 'sendFeedback'),
9898
gzip: true,
99-
limit: '29 KB',
99+
limit: '30 KB',
100100
},
101101
{
102102
name: '@sentry/browser (incl. FeedbackAsync)',
@@ -150,13 +150,13 @@ module.exports = [
150150
name: 'CDN Bundle',
151151
path: createCDNPath('bundle.min.js'),
152152
gzip: true,
153-
limit: '26 KB',
153+
limit: '27 KB',
154154
},
155155
{
156156
name: 'CDN Bundle (incl. Tracing)',
157157
path: createCDNPath('bundle.tracing.min.js'),
158158
gzip: true,
159-
limit: '41 KB',
159+
limit: '42 KB',
160160
},
161161
{
162162
name: 'CDN Bundle (incl. Tracing, Replay)',
@@ -183,7 +183,7 @@ module.exports = [
183183
path: createCDNPath('bundle.tracing.min.js'),
184184
gzip: false,
185185
brotli: false,
186-
limit: '120 KB',
186+
limit: '123 KB',
187187
},
188188
{
189189
name: 'CDN Bundle (incl. Tracing, Replay) - uncompressed',
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
_experiments: {
8+
enableMetrics: true,
9+
},
10+
release: '1.0.0',
11+
environment: 'test',
12+
integrations: integrations => {
13+
return integrations.filter(integration => integration.name !== 'BrowserSession');
14+
},
15+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Sentry.metrics.count('test.counter', 1, { attributes: { endpoint: '/api/test' } });
2+
Sentry.metrics.gauge('test.gauge', 42, { unit: 'millisecond', attributes: { server: 'test-1' } });
3+
Sentry.metrics.distribution('test.distribution', 200, { unit: 'second', attributes: { priority: 'high' } });
4+
5+
Sentry.startSpan({ name: 'test-span', op: 'test' }, () => {
6+
Sentry.metrics.count('test.span.counter', 1, { attributes: { operation: 'test' } });
7+
});
8+
9+
Sentry.setUser({ id: 'user-123', email: '[email protected]', username: 'testuser' });
10+
Sentry.metrics.count('test.user.counter', 1, { attributes: { action: 'click' } });
11+
12+
Sentry.flush();
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { expect } from '@playwright/test';
2+
import type { MetricEnvelope } from '@sentry/core';
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { getFirstSentryEnvelopeRequest, properFullEnvelopeRequestParser } from '../../../../utils/helpers';
5+
6+
sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) => {
7+
const bundle = process.env.PW_BUNDLE || '';
8+
if (bundle.startsWith('bundle') || bundle.startsWith('loader')) {
9+
sentryTest.skip();
10+
}
11+
12+
const url = await getLocalTestUrl({ testDir: __dirname });
13+
14+
const event = await getFirstSentryEnvelopeRequest<MetricEnvelope>(page, url, properFullEnvelopeRequestParser);
15+
const envelopeItems = event[1];
16+
17+
expect(envelopeItems[0]).toEqual([
18+
{
19+
type: 'trace_metric',
20+
item_count: 5,
21+
content_type: 'application/vnd.sentry.items.trace-metric+json',
22+
},
23+
{
24+
items: [
25+
{
26+
timestamp: expect.any(Number),
27+
trace_id: expect.any(String),
28+
name: 'test.counter',
29+
type: 'counter',
30+
value: 1,
31+
attributes: {
32+
endpoint: { value: '/api/test', type: 'string' },
33+
'sentry.release': { value: '1.0.0', type: 'string' },
34+
'sentry.environment': { value: 'test', type: 'string' },
35+
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
36+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
37+
},
38+
},
39+
{
40+
timestamp: expect.any(Number),
41+
trace_id: expect.any(String),
42+
name: 'test.gauge',
43+
type: 'gauge',
44+
unit: 'millisecond',
45+
value: 42,
46+
attributes: {
47+
server: { value: 'test-1', type: 'string' },
48+
'sentry.release': { value: '1.0.0', type: 'string' },
49+
'sentry.environment': { value: 'test', type: 'string' },
50+
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
51+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
52+
},
53+
},
54+
{
55+
timestamp: expect.any(Number),
56+
trace_id: expect.any(String),
57+
name: 'test.distribution',
58+
type: 'distribution',
59+
unit: 'second',
60+
value: 200,
61+
attributes: {
62+
priority: { value: 'high', type: 'string' },
63+
'sentry.release': { value: '1.0.0', type: 'string' },
64+
'sentry.environment': { value: 'test', type: 'string' },
65+
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
66+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
67+
},
68+
},
69+
{
70+
timestamp: expect.any(Number),
71+
trace_id: expect.any(String),
72+
span_id: expect.any(String),
73+
name: 'test.span.counter',
74+
type: 'counter',
75+
value: 1,
76+
attributes: {
77+
operation: { value: 'test', type: 'string' },
78+
'sentry.release': { value: '1.0.0', type: 'string' },
79+
'sentry.environment': { value: 'test', type: 'string' },
80+
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
81+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
82+
},
83+
},
84+
{
85+
timestamp: expect.any(Number),
86+
trace_id: expect.any(String),
87+
name: 'test.user.counter',
88+
type: 'counter',
89+
value: 1,
90+
attributes: {
91+
action: { value: 'click', type: 'string' },
92+
'user.id': { value: 'user-123', type: 'string' },
93+
'user.email': { value: '[email protected]', type: 'string' },
94+
'user.name': { value: 'testuser', type: 'string' },
95+
'sentry.release': { value: '1.0.0', type: 'string' },
96+
'sentry.environment': { value: 'test', type: 'string' },
97+
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
98+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
99+
},
100+
},
101+
],
102+
},
103+
]);
104+
});

dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ const DEPENDENTS: Dependent[] = [
4141
ignoreExports: [
4242
// Not needed for Astro
4343
'setupFastifyErrorHandler',
44+
// Todo(metrics): Add metrics exports for beta
45+
'metrics',
4446
],
4547
},
4648
{
@@ -54,6 +56,8 @@ const DEPENDENTS: Dependent[] = [
5456
'childProcessIntegration',
5557
'systemErrorIntegration',
5658
'pinoIntegration',
59+
// Todo(metrics): Add metrics exports for beta
60+
'metrics',
5761
],
5862
},
5963
{
@@ -75,6 +79,8 @@ const DEPENDENTS: Dependent[] = [
7579
ignoreExports: [
7680
// Not needed for Serverless
7781
'setupFastifyErrorHandler',
82+
// Todo(metrics): Add metrics exports for beta
83+
'metrics',
7884
],
7985
},
8086
{
@@ -84,6 +90,8 @@ const DEPENDENTS: Dependent[] = [
8490
ignoreExports: [
8591
// Not needed for Serverless
8692
'setupFastifyErrorHandler',
93+
// Todo(metrics): Add metrics exports for beta
94+
'metrics',
8795
],
8896
},
8997
{
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import * as Sentry from '@sentry/node-core';
2+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
3+
import { setupOtel } from '../../../utils/setupOtel';
4+
5+
const client = Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
release: '1.0.0',
8+
environment: 'test',
9+
_experiments: {
10+
enableMetrics: true,
11+
},
12+
transport: loggingTransport,
13+
});
14+
15+
setupOtel(client);
16+
17+
async function run(): Promise<void> {
18+
Sentry.metrics.count('test.counter', 1, { attributes: { endpoint: '/api/test' } });
19+
20+
Sentry.metrics.gauge('test.gauge', 42, { unit: 'millisecond', attributes: { server: 'test-1' } });
21+
22+
Sentry.metrics.distribution('test.distribution', 200, { unit: 'second', attributes: { priority: 'high' } });
23+
24+
await Sentry.startSpan({ name: 'test-span', op: 'test' }, async () => {
25+
Sentry.metrics.count('test.span.counter', 1, { attributes: { operation: 'test' } });
26+
});
27+
28+
Sentry.setUser({ id: 'user-123', email: '[email protected]', username: 'testuser' });
29+
Sentry.metrics.count('test.user.counter', 1, { attributes: { action: 'click' } });
30+
31+
await Sentry.flush();
32+
}
33+
34+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
35+
void run();
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { afterAll, describe, expect, test } from 'vitest';
2+
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';
3+
4+
describe('metrics', () => {
5+
afterAll(() => {
6+
cleanupChildProcesses();
7+
});
8+
9+
test('should capture all metric types', async () => {
10+
const runner = createRunner(__dirname, 'scenario.ts')
11+
.unignore('trace_metric')
12+
.expect({
13+
trace_metric: {
14+
items: [
15+
{
16+
timestamp: expect.any(Number),
17+
trace_id: expect.any(String),
18+
name: 'test.counter',
19+
type: 'counter',
20+
value: 1,
21+
attributes: {
22+
endpoint: { value: '/api/test', type: 'string' },
23+
'sentry.release': { value: '1.0.0', type: 'string' },
24+
'sentry.environment': { value: 'test', type: 'string' },
25+
'sentry.sdk.name': { value: 'sentry.javascript.node-core', type: 'string' },
26+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
27+
},
28+
},
29+
{
30+
timestamp: expect.any(Number),
31+
trace_id: expect.any(String),
32+
name: 'test.gauge',
33+
type: 'gauge',
34+
unit: 'millisecond',
35+
value: 42,
36+
attributes: {
37+
server: { value: 'test-1', type: 'string' },
38+
'sentry.release': { value: '1.0.0', type: 'string' },
39+
'sentry.environment': { value: 'test', type: 'string' },
40+
'sentry.sdk.name': { value: 'sentry.javascript.node-core', type: 'string' },
41+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
42+
},
43+
},
44+
{
45+
timestamp: expect.any(Number),
46+
trace_id: expect.any(String),
47+
name: 'test.distribution',
48+
type: 'distribution',
49+
unit: 'second',
50+
value: 200,
51+
attributes: {
52+
priority: { value: 'high', type: 'string' },
53+
'sentry.release': { value: '1.0.0', type: 'string' },
54+
'sentry.environment': { value: 'test', type: 'string' },
55+
'sentry.sdk.name': { value: 'sentry.javascript.node-core', type: 'string' },
56+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
57+
},
58+
},
59+
{
60+
timestamp: expect.any(Number),
61+
trace_id: expect.any(String),
62+
name: 'test.span.counter',
63+
type: 'counter',
64+
value: 1,
65+
attributes: {
66+
operation: { value: 'test', type: 'string' },
67+
'sentry.release': { value: '1.0.0', type: 'string' },
68+
'sentry.environment': { value: 'test', type: 'string' },
69+
'sentry.sdk.name': { value: 'sentry.javascript.node-core', type: 'string' },
70+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
71+
},
72+
},
73+
{
74+
timestamp: expect.any(Number),
75+
trace_id: expect.any(String),
76+
name: 'test.user.counter',
77+
type: 'counter',
78+
value: 1,
79+
attributes: {
80+
action: { value: 'click', type: 'string' },
81+
'user.id': { value: 'user-123', type: 'string' },
82+
'user.email': { value: '[email protected]', type: 'string' },
83+
'user.name': { value: 'testuser', type: 'string' },
84+
'sentry.release': { value: '1.0.0', type: 'string' },
85+
'sentry.environment': { value: 'test', type: 'string' },
86+
'sentry.sdk.name': { value: 'sentry.javascript.node-core', type: 'string' },
87+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
88+
},
89+
},
90+
],
91+
},
92+
})
93+
.start();
94+
95+
await runner.completed();
96+
});
97+
});

dev-packages/node-core-integration-tests/utils/assertions.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
Event,
55
SerializedCheckIn,
66
SerializedLogContainer,
7+
SerializedMetricContainer,
78
SerializedSession,
89
SessionAggregates,
910
TransactionEvent,
@@ -76,6 +77,15 @@ export function assertSentryLogContainer(
7677
});
7778
}
7879

80+
export function assertSentryMetricContainer(
81+
actual: SerializedMetricContainer,
82+
expected: Partial<SerializedMetricContainer>,
83+
): void {
84+
expect(actual).toMatchObject({
85+
...expected,
86+
});
87+
}
88+
7989
export function assertEnvelopeHeader(actual: Envelope[0], expected: Partial<Envelope[0]>): void {
8090
expect(actual).toEqual({
8191
event_id: expect.any(String),

0 commit comments

Comments
 (0)