Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client';

import * as Sentry from '@sentry/nextjs';

export default function Page() {
const handleClick = async () => {
Sentry.metrics.count('test.page.count', 1, {
attributes: {
page: '/metrics',
'random.attribute': 'Apples',
},
});
Sentry.metrics.distribution('test.page.distribution', 100, {
attributes: {
page: '/metrics',
'random.attribute': 'Manzanas',
},
});
Sentry.metrics.gauge('test.page.gauge', 200, {
attributes: {
page: '/metrics',
'random.attribute': 'Mele',
},
});
await fetch('/metrics/route-handler');
};

return (
<div>
<h1>Metrics page</h1>
<button onClick={handleClick}>Emit</button>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as Sentry from '@sentry/nextjs';

export const GET = async () => {
Sentry.metrics.count('test.route.handler.count', 1, {
attributes: {
endpoint: '/metrics/route-handler',
'random.attribute': 'Potatoes',
},
});
Sentry.metrics.distribution('test.route.handler.distribution', 100, {
attributes: {
endpoint: '/metrics/route-handler',
'random.attribute': 'Patatas',
},
});
Sentry.metrics.gauge('test.route.handler.gauge', 200, {
attributes: {
endpoint: '/metrics/route-handler',
'random.attribute': 'Patate',
},
});
return Response.json({ message: 'Bueno' });
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { expect, test } from '@playwright/test';
import { waitForMetric } from '@sentry-internal/test-utils';

test('Should emit metrics from server and client', async ({ request, page }) => {
const clientCountPromise = waitForMetric('nextjs-16', async metric => {
return metric.name === 'test.page.count';
});

const clientDistributionPromise = waitForMetric('nextjs-16', async metric => {
return metric.name === 'test.page.distribution';
});

const clientGaugePromise = waitForMetric('nextjs-16', async metric => {
return metric.name === 'test.page.gauge';
});

const serverCountPromise = waitForMetric('nextjs-16', async metric => {
return metric.name === 'test.route.handler.count';
});

const serverDistributionPromise = waitForMetric('nextjs-16', async metric => {
return metric.name === 'test.route.handler.distribution';
});

const serverGaugePromise = waitForMetric('nextjs-16', async metric => {
return metric.name === 'test.route.handler.gauge';
});

await page.goto('/metrics');
await page.getByText('Emit').click();
const clientCount = await clientCountPromise;
const clientDistribution = await clientDistributionPromise;
const clientGauge = await clientGaugePromise;
const serverCount = await serverCountPromise;
const serverDistribution = await serverDistributionPromise;
const serverGauge = await serverGaugePromise;

expect(clientCount).toMatchObject({
timestamp: expect.any(Number),
trace_id: expect.any(String),
span_id: expect.any(String),
name: 'test.page.count',
type: 'counter',
value: 1,
attributes: {
page: { value: '/metrics', type: 'string' },
'random.attribute': { value: 'Apples', type: 'string' },
'sentry.environment': { value: 'qa', type: 'string' },
'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
},
});

expect(clientDistribution).toMatchObject({
timestamp: expect.any(Number),
trace_id: expect.any(String),
span_id: expect.any(String),
name: 'test.page.distribution',
type: 'distribution',
value: 100,
attributes: {
page: { value: '/metrics', type: 'string' },
'random.attribute': { value: 'Manzanas', type: 'string' },
'sentry.environment': { value: 'qa', type: 'string' },
'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
},
});

expect(clientGauge).toMatchObject({
timestamp: expect.any(Number),
trace_id: expect.any(String),
span_id: expect.any(String),
name: 'test.page.gauge',
type: 'gauge',
value: 200,
attributes: {
page: { value: '/metrics', type: 'string' },
'random.attribute': { value: 'Mele', type: 'string' },
'sentry.environment': { value: 'qa', type: 'string' },
'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
},
});

expect(serverCount).toMatchObject({
timestamp: expect.any(Number),
trace_id: expect.any(String),
name: 'test.route.handler.count',
type: 'counter',
value: 1,
attributes: {
'server.address': { value: expect.any(String), type: 'string' },
'random.attribute': { value: 'Potatoes', type: 'string' },
endpoint: { value: '/metrics/route-handler', type: 'string' },
'sentry.environment': { value: 'qa', type: 'string' },
'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
},
});

expect(serverDistribution).toMatchObject({
timestamp: expect.any(Number),
trace_id: expect.any(String),
name: 'test.route.handler.distribution',
type: 'distribution',
value: 100,
attributes: {
'server.address': { value: expect.any(String), type: 'string' },
'random.attribute': { value: 'Patatas', type: 'string' },
endpoint: { value: '/metrics/route-handler', type: 'string' },
'sentry.environment': { value: 'qa', type: 'string' },
'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
},
});

expect(serverGauge).toMatchObject({
timestamp: expect.any(Number),
trace_id: expect.any(String),
name: 'test.route.handler.gauge',
type: 'gauge',
value: 200,
attributes: {
'server.address': { value: expect.any(String), type: 'string' },
'random.attribute': { value: 'Patate', type: 'string' },
endpoint: { value: '/metrics/route-handler', type: 'string' },
'sentry.environment': { value: 'qa', type: 'string' },
'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
},
});
});
38 changes: 37 additions & 1 deletion dev-packages/test-utils/src/event-proxy-server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
/* eslint-disable max-lines */
import type { Envelope, EnvelopeItem, Event, SerializedSession } from '@sentry/core';
import type {
Envelope,
EnvelopeItem,
Event,
SerializedMetric,
SerializedMetricContainer,
SerializedSession,
} from '@sentry/core';
import { parseEnvelope } from '@sentry/core';
import * as fs from 'fs';
import * as http from 'http';
Expand Down Expand Up @@ -391,6 +398,35 @@ export function waitForTransaction(
});
}

/**
* Wait for metric items to be sent.
*/
export function waitForMetric(
proxyServerName: string,
callback: (metricEvent: SerializedMetric) => Promise<boolean> | boolean,
): Promise<SerializedMetric> {
const timestamp = getNanosecondTimestamp();
return new Promise((resolve, reject) => {
waitForEnvelopeItem(
proxyServerName,
async envelopeItem => {
const [envelopeItemHeader, envelopeItemBody] = envelopeItem;
const metricContainer = envelopeItemBody as SerializedMetricContainer;
if (envelopeItemHeader.type === 'trace_metric') {
for (const metric of metricContainer.items) {
if (await callback(metric)) {
resolve(metric);
return true;
}
}
}
return false;
},
timestamp,
).catch(reject);
});
}

const TEMP_FILE_PREFIX = 'event-proxy-server-';

async function registerCallbackServerPort(serverName: string, port: string): Promise<void> {
Expand Down
1 change: 1 addition & 0 deletions dev-packages/test-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export {
waitForTransaction,
waitForSession,
waitForPlainRequest,
waitForMetric,
} from './event-proxy-server';

export { getPlaywrightConfig } from './playwright-config';
Expand Down
Loading