Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(node): Ensure node-fetch does not emit spans without tracing #13765

Merged
merged 1 commit into from
Sep 24, 2024
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
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,7 @@ jobs:
'node-express-cjs-preload',
'node-otel-sdk-node',
'node-otel-custom-sampler',
'node-otel-without-tracing',
'ember-classic',
'ember-embroider',
'nextjs-app-dir',
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ local.log
.rpt2_cache

lint-results.json
trace.zip

# legacy
tmp.js
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
"test:assert": "pnpm test"
},
"dependencies": {
"@opentelemetry/sdk-trace-node": "1.25.1",
"@opentelemetry/exporter-trace-otlp-http": "0.52.1",
"@opentelemetry/instrumentation-undici": "0.4.0",
"@opentelemetry/instrumentation": "0.52.1",
"@opentelemetry/sdk-trace-node": "1.26.0",
"@opentelemetry/exporter-trace-otlp-http": "0.53.0",
"@opentelemetry/instrumentation-undici": "0.6.0",
"@opentelemetry/instrumentation": "0.53.0",
"@sentry/core": "latest || *",
"@sentry/node": "latest || *",
"@sentry/opentelemetry": "latest || *",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const { SentrySpanProcessor, SentryPropagator } = require('@sentry/opentelemetry
const { UndiciInstrumentation } = require('@opentelemetry/instrumentation-undici');
const { registerInstrumentations } = require('@opentelemetry/instrumentation');

const sentryClient = Sentry.init({
Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
dsn:
process.env.E2E_TEST_DSN ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ test('Sends an API route transaction to OTLP', async ({ baseURL }) => {

const scopeSpans = json.resourceSpans?.[0]?.scopeSpans;

const httpScope = scopeSpans?.find(scopeSpan => scopeSpan.scope.name === '@opentelemetry/instrumentation-http');
const httpScope = scopeSpans?.find(
scopeSpan => scopeSpan.scope.name === '@opentelemetry_sentry-patched/instrumentation-http',
);

return (
httpScope &&
Expand All @@ -22,7 +24,7 @@ test('Sends an API route transaction to OTLP', async ({ baseURL }) => {
);
});

await fetch(`${baseURL}/test-transaction`);
fetch(`${baseURL}/test-transaction`);

const otelData = await otelPromise;

Expand All @@ -38,7 +40,9 @@ test('Sends an API route transaction to OTLP', async ({ baseURL }) => {
// But our default node-fetch spans are not emitted
expect(scopeSpans.length).toEqual(2);

const httpScopes = scopeSpans?.filter(scopeSpan => scopeSpan.scope.name === '@opentelemetry/instrumentation-http');
const httpScopes = scopeSpans?.filter(
scopeSpan => scopeSpan.scope.name === '@opentelemetry_sentry-patched/instrumentation-http',
);
const undiciScopes = scopeSpans?.filter(
scopeSpan => scopeSpan.scope.name === '@opentelemetry/instrumentation-undici',
);
Expand All @@ -49,6 +53,38 @@ test('Sends an API route transaction to OTLP', async ({ baseURL }) => {
expect(undiciScopes.length).toBe(1);
expect(undiciScopes[0].spans.length).toBe(1);

expect(undiciScopes[0].spans).toEqual([
{
traceId: expect.any(String),
spanId: expect.any(String),
name: 'GET',
kind: 3,
startTimeUnixNano: expect.any(String),
endTimeUnixNano: expect.any(String),
attributes: expect.arrayContaining([
{ key: 'http.request.method', value: { stringValue: 'GET' } },
{ key: 'http.request.method_original', value: { stringValue: 'GET' } },
{ key: 'url.full', value: { stringValue: 'http://localhost:3030/test-success' } },
{ key: 'url.path', value: { stringValue: '/test-success' } },
{ key: 'url.query', value: { stringValue: '' } },
{ key: 'url.scheme', value: { stringValue: 'http' } },
{ key: 'server.address', value: { stringValue: 'localhost' } },
{ key: 'server.port', value: { intValue: 3030 } },
{ key: 'user_agent.original', value: { stringValue: 'node' } },
{ key: 'network.peer.address', value: { stringValue: expect.any(String) } },
{ key: 'network.peer.port', value: { intValue: 3030 } },
{ key: 'http.response.status_code', value: { intValue: 200 } },
{ key: 'http.response.header.content-length', value: { intValue: 16 } },
]),
droppedAttributesCount: 0,
events: [],
droppedEventsCount: 0,
status: { code: 0 },
links: [],
droppedLinksCount: 0,
},
]);

// There may be another span from another request, we can ignore that
const httpSpans = httpScopes[0].spans.filter(span =>
span.attributes.some(attr => attr.key === 'http.target' && attr.value?.stringValue === '/test-transaction'),
Expand All @@ -62,104 +98,24 @@ test('Sends an API route transaction to OTLP', async ({ baseURL }) => {
kind: 2,
startTimeUnixNano: expect.any(String),
endTimeUnixNano: expect.any(String),
attributes: [
{
key: 'http.url',
value: {
stringValue: 'http://localhost:3030/test-transaction',
},
},
{
key: 'http.host',
value: {
stringValue: 'localhost:3030',
},
},
{
key: 'net.host.name',
value: {
stringValue: 'localhost',
},
},
{
key: 'http.method',
value: {
stringValue: 'GET',
},
},
{
key: 'http.scheme',
value: {
stringValue: 'http',
},
},
{
key: 'http.target',
value: {
stringValue: '/test-transaction',
},
},
{
key: 'http.user_agent',
value: {
stringValue: 'node',
},
},
{
key: 'http.flavor',
value: {
stringValue: '1.1',
},
},
{
key: 'net.transport',
value: {
stringValue: 'ip_tcp',
},
},
{
key: 'sentry.origin',
value: {
stringValue: 'auto.http.otel.http',
},
},
{
key: 'net.host.ip',
value: {
stringValue: expect.any(String),
},
},
{
key: 'net.host.port',
value: {
intValue: 3030,
},
},
{
key: 'net.peer.ip',
value: {
stringValue: expect.any(String),
},
},
{
key: 'net.peer.port',
value: {
intValue: expect.any(Number),
},
},
{
key: 'http.status_code',
value: {
intValue: 200,
},
},
{
key: 'http.status_text',
value: {
stringValue: 'OK',
},
},
],
attributes: expect.arrayContaining([
{ key: 'http.url', value: { stringValue: 'http://localhost:3030/test-transaction' } },
{ key: 'http.host', value: { stringValue: 'localhost:3030' } },
{ key: 'net.host.name', value: { stringValue: 'localhost' } },
{ key: 'http.method', value: { stringValue: 'GET' } },
{ key: 'http.scheme', value: { stringValue: 'http' } },
{ key: 'http.target', value: { stringValue: '/test-transaction' } },
{ key: 'http.user_agent', value: { stringValue: 'node' } },
{ key: 'http.flavor', value: { stringValue: '1.1' } },
{ key: 'net.transport', value: { stringValue: 'ip_tcp' } },
{ key: 'net.host.ip', value: { stringValue: expect.any(String) } },
{ key: 'net.host.port', value: { intValue: 3030 } },
{ key: 'net.peer.ip', value: { stringValue: expect.any(String) } },
{ key: 'net.peer.port', value: { intValue: expect.any(Number) } },
{ key: 'http.status_code', value: { intValue: 200 } },
{ key: 'http.status_text', value: { stringValue: 'OK' } },
{ key: 'sentry.origin', value: { stringValue: 'auto.http.otel.http' } },
]),
droppedAttributesCount: 0,
events: [],
droppedEventsCount: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ async function run(): Promise<void> {
Sentry.addBreadcrumb({ message: 'manual breadcrumb' });

await makeHttpRequest(`${process.env.SERVER_URL}/api/v0`);
await makeHttpRequest(`${process.env.SERVER_URL}/api/v1`);
await makeHttpGet(`${process.env.SERVER_URL}/api/v1`);
await makeHttpRequest(`${process.env.SERVER_URL}/api/v2`);
await makeHttpRequest(`${process.env.SERVER_URL}/api/v3`);

Expand All @@ -46,3 +46,16 @@ function makeHttpRequest(url: string): Promise<void> {
.end();
});
}

function makeHttpGet(url: string): Promise<void> {
return new Promise<void>(resolve => {
http.get(url, httpRes => {
httpRes.on('data', () => {
// we don't care about data
});
httpRes.on('end', () => {
resolve();
});
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import * as http from 'http';

async function run(): Promise<void> {
await makeHttpRequest(`${process.env.SERVER_URL}/api/v0`);
await makeHttpRequest(`${process.env.SERVER_URL}/api/v1`);
await makeHttpGet(`${process.env.SERVER_URL}/api/v1`);
await makeHttpRequest(`${process.env.SERVER_URL}/api/v2`);
await makeHttpRequest(`${process.env.SERVER_URL}/api/v3`);

Expand All @@ -37,3 +37,16 @@ function makeHttpRequest(url: string): Promise<void> {
.end();
});
}

function makeHttpGet(url: string): Promise<void> {
return new Promise<void>(resolve => {
http.get(url, httpRes => {
httpRes.on('data', () => {
// we don't care about data
});
httpRes.on('end', () => {
resolve();
});
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,56 @@ test('outgoing http requests are correctly instrumented with tracing disabled',
},
],
},
breadcrumbs: [
{
message: 'manual breadcrumb',
timestamp: expect.any(Number),
},
{
category: 'http',
data: {
'http.method': 'GET',
url: `${SERVER_URL}/api/v0`,
status_code: 404,
ADDED_PATH: '/api/v0',
},
timestamp: expect.any(Number),
type: 'http',
},
{
category: 'http',
data: {
'http.method': 'GET',
url: `${SERVER_URL}/api/v1`,
status_code: 404,
ADDED_PATH: '/api/v1',
},
timestamp: expect.any(Number),
type: 'http',
},
{
category: 'http',
data: {
'http.method': 'GET',
url: `${SERVER_URL}/api/v2`,
status_code: 404,
ADDED_PATH: '/api/v2',
},
timestamp: expect.any(Number),
type: 'http',
},
{
category: 'http',
data: {
'http.method': 'GET',
url: `${SERVER_URL}/api/v3`,
status_code: 404,
ADDED_PATH: '/api/v3',
},
timestamp: expect.any(Number),
type: 'http',
},
],
},
})
.start(closeTestServer);
Expand Down
Loading
Loading