Skip to content

Commit e749085

Browse files
committed
Add node-core otel v2 e2e test app
1 parent ba1686b commit e749085

File tree

10 files changed

+291
-0
lines changed

10 files changed

+291
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@sentry:registry=http://127.0.0.1:4873
2+
@sentry-internal:registry=http://127.0.0.1:4873
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "node-core-express-otel-v2-app",
3+
"version": "1.0.0",
4+
"private": true,
5+
"scripts": {
6+
"build": "tsc",
7+
"start": "node dist/app.js",
8+
"test": "playwright test",
9+
"clean": "npx rimraf node_modules pnpm-lock.yaml",
10+
"test:build": "pnpm install && pnpm build",
11+
"test:assert": "pnpm test"
12+
},
13+
"dependencies": {
14+
"@sentry/node-core": "latest || *",
15+
"@sentry/opentelemetry": "latest || *",
16+
"@opentelemetry/api": "^1.9.0",
17+
"@opentelemetry/context-async-hooks": "^2.0.0",
18+
"@opentelemetry/core": "^2.0.0",
19+
"@opentelemetry/instrumentation": "^0.200.0",
20+
"@opentelemetry/instrumentation-http": "^0.200.0",
21+
"@opentelemetry/resources": "^2.0.0",
22+
"@opentelemetry/sdk-trace-node": "^2.0.0",
23+
"@opentelemetry/semantic-conventions": "^1.30.0",
24+
"@types/express": "^4.17.21",
25+
"@types/node": "^18.19.1",
26+
"express": "^4.21.2",
27+
"typescript": "~5.0.0"
28+
},
29+
"devDependencies": {
30+
"@playwright/test": "~1.50.0",
31+
"@sentry-internal/test-utils": "link:../../../test-utils"
32+
},
33+
"resolutions": {
34+
"@types/qs": "6.9.17"
35+
},
36+
"volta": {
37+
"extends": "../../package.json"
38+
}
39+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { getPlaywrightConfig } from '@sentry-internal/test-utils';
2+
3+
const config = getPlaywrightConfig({
4+
startCommand: `pnpm start`,
5+
});
6+
7+
export default config;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Import this first!
2+
import './instrument';
3+
4+
// Now import other modules
5+
import * as Sentry from '@sentry/node-core';
6+
import express from 'express';
7+
8+
const app = express();
9+
const port = 3030;
10+
11+
app.get('/test-transaction', function (req, res) {
12+
Sentry.withActiveSpan(null, async () => {
13+
Sentry.startSpan({ name: 'test-transaction', op: 'e2e-test' }, () => {
14+
Sentry.startSpan({ name: 'test-span' }, () => undefined);
15+
});
16+
17+
await Sentry.flush();
18+
19+
res.send({
20+
transactionIds: global.transactionIds || [],
21+
});
22+
});
23+
});
24+
25+
app.get('/test-exception/:id', function (req, _res) {
26+
try {
27+
throw new Error(`This is an exception with id ${req.params.id}`);
28+
} catch (e) {
29+
Sentry.captureException(e);
30+
throw e;
31+
}
32+
});
33+
34+
app.get('/test-local-variables-caught', function (req, res) {
35+
const randomVariableToRecord = Math.random();
36+
37+
let exceptionId: string;
38+
try {
39+
throw new Error('Local Variable Error');
40+
} catch (e) {
41+
exceptionId = Sentry.captureException(e);
42+
}
43+
44+
res.send({ exceptionId, randomVariableToRecord });
45+
});
46+
47+
// @ts-ignore
48+
app.use(function onError(err, req, res, next) {
49+
// The error id is attached to `res.sentry` to be returned
50+
// and optionally displayed to the user for support.
51+
res.statusCode = 500;
52+
res.end(res.sentry + '\n');
53+
});
54+
55+
app.listen(port, () => {
56+
console.log(`Example app listening on port ${port}`);
57+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
2+
import * as Sentry from '@sentry/node-core';
3+
import { SentrySpanProcessor, SentryPropagator, SentrySampler } from '@sentry/opentelemetry';
4+
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
5+
6+
declare global {
7+
namespace globalThis {
8+
var transactionIds: string[];
9+
}
10+
}
11+
12+
const sentryClient = Sentry.init({
13+
environment: 'qa', // dynamic sampling bias to keep transactions
14+
dsn: process.env.E2E_TEST_DSN,
15+
includeLocalVariables: true,
16+
debug: !!process.env.DEBUG,
17+
tunnel: `http://localhost:3031/`, // proxy server
18+
tracesSampleRate: 1,
19+
openTelemetryInstrumentations: [new HttpInstrumentation()],
20+
});
21+
22+
const provider = new NodeTracerProvider({
23+
sampler: sentryClient ? new SentrySampler(sentryClient) : undefined,
24+
spanProcessors: [new SentrySpanProcessor()],
25+
});
26+
27+
provider.register({
28+
propagator: new SentryPropagator(),
29+
contextManager: new Sentry.SentryContextManager(),
30+
});
31+
32+
Sentry.validateOpenTelemetrySetup();
33+
34+
Sentry.addEventProcessor(event => {
35+
global.transactionIds = global.transactionIds || [];
36+
37+
if (event.type === 'transaction') {
38+
const eventId = event.event_id;
39+
40+
if (eventId) {
41+
global.transactionIds.push(eventId);
42+
}
43+
}
44+
45+
return event;
46+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { startEventProxyServer } from '@sentry-internal/test-utils';
2+
3+
startEventProxyServer({
4+
port: 3031,
5+
proxyServerName: 'node-core-express-otel-v2',
6+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { expect, test } from '@playwright/test';
2+
import { waitForError } from '@sentry-internal/test-utils';
3+
4+
test('Sends correct error event', async ({ baseURL }) => {
5+
const errorEventPromise = waitForError('node-core-express-otel-v2', event => {
6+
return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123';
7+
});
8+
9+
await fetch(`${baseURL}/test-exception/123`);
10+
11+
const errorEvent = await errorEventPromise;
12+
13+
expect(errorEvent.exception?.values).toHaveLength(1);
14+
expect(errorEvent.exception?.values?.[0]?.value).toBe('This is an exception with id 123');
15+
16+
expect(errorEvent.request).toEqual({
17+
method: 'GET',
18+
cookies: {},
19+
headers: expect.any(Object),
20+
url: 'http://localhost:3030/test-exception/123',
21+
});
22+
23+
expect(errorEvent.transaction).toEqual('GET /test-exception/123');
24+
25+
expect(errorEvent.contexts?.trace).toEqual({
26+
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
27+
span_id: expect.stringMatching(/[a-f0-9]{16}/),
28+
});
29+
});
30+
31+
test('Should record caught exceptions with local variable', async ({ baseURL }) => {
32+
const errorEventPromise = waitForError('node-core-express-otel-v2', event => {
33+
return event.transaction === 'GET /test-local-variables-caught';
34+
});
35+
36+
await fetch(`${baseURL}/test-local-variables-caught`);
37+
38+
const errorEvent = await errorEventPromise;
39+
40+
const frames = errorEvent.exception?.values?.[0].stacktrace?.frames;
41+
expect(frames?.[frames.length - 1].vars?.randomVariableToRecord).toBeDefined();
42+
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { expect, test } from '@playwright/test';
2+
import { waitForTransaction } from '@sentry-internal/test-utils';
3+
4+
test('Sends an API route transaction', async ({ baseURL }) => {
5+
const pageloadTransactionEventPromise = waitForTransaction('node-core-express-otel-v2', transactionEvent => {
6+
return (
7+
transactionEvent?.contexts?.trace?.op === 'http.server' &&
8+
transactionEvent?.transaction === 'GET /test-transaction'
9+
);
10+
});
11+
12+
await fetch(`${baseURL}/test-transaction`);
13+
14+
const transactionEvent = await pageloadTransactionEventPromise;
15+
16+
expect(transactionEvent.contexts?.trace).toEqual({
17+
data: {
18+
'sentry.source': 'url',
19+
'sentry.origin': 'manual',
20+
'sentry.op': 'http.server',
21+
'sentry.sample_rate': 1,
22+
url: 'http://localhost:3030/test-transaction',
23+
'otel.kind': 'SERVER',
24+
'http.response.status_code': 200,
25+
'http.url': 'http://localhost:3030/test-transaction',
26+
'http.host': 'localhost:3030',
27+
'net.host.name': 'localhost',
28+
'http.method': 'GET',
29+
'http.scheme': 'http',
30+
'http.target': '/test-transaction',
31+
'http.user_agent': 'node',
32+
'http.flavor': '1.1',
33+
'net.transport': 'ip_tcp',
34+
'net.host.ip': expect.any(String),
35+
'net.host.port': expect.any(Number),
36+
'net.peer.ip': expect.any(String),
37+
'net.peer.port': expect.any(Number),
38+
'http.status_code': 200,
39+
'http.status_text': 'OK',
40+
},
41+
op: 'http.server',
42+
span_id: expect.stringMatching(/[a-f0-9]{16}/),
43+
status: 'ok',
44+
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
45+
origin: 'manual',
46+
});
47+
48+
expect(transactionEvent.contexts?.response).toEqual({
49+
status_code: 200,
50+
});
51+
52+
expect(transactionEvent).toEqual(
53+
expect.objectContaining({
54+
transaction: 'GET /test-transaction',
55+
type: 'transaction',
56+
transaction_info: {
57+
source: 'url',
58+
},
59+
}),
60+
);
61+
});
62+
63+
test('Sends an API route transaction for an errored route', async ({ baseURL }) => {
64+
const transactionEventPromise = waitForTransaction('node-core-express-otel-v2', transactionEvent => {
65+
return (
66+
transactionEvent.contexts?.trace?.op === 'http.server' &&
67+
transactionEvent.transaction === 'GET /test-exception/777' &&
68+
transactionEvent.request?.url === 'http://localhost:3030/test-exception/777'
69+
);
70+
});
71+
72+
await fetch(`${baseURL}/test-exception/777`);
73+
74+
const transactionEvent = await transactionEventPromise;
75+
76+
expect(transactionEvent.contexts?.trace?.op).toEqual('http.server');
77+
expect(transactionEvent.transaction).toEqual('GET /test-exception/777');
78+
expect(transactionEvent.contexts?.trace?.status).toEqual('internal_error');
79+
expect(transactionEvent.contexts?.trace?.data?.['http.status_code']).toEqual(500);
80+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"compilerOptions": {
3+
"types": ["node"],
4+
"esModuleInterop": true,
5+
"lib": ["es2020"],
6+
"strict": true,
7+
"outDir": "dist",
8+
"skipLibCheck": true
9+
},
10+
"include": ["src/**/*.ts"]
11+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy