Skip to content

fix(node): Ensure correct URL is passed to ignoreIncomingRequests callback #12929

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

Merged
merged 5 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
10 changes: 7 additions & 3 deletions dev-packages/node-integration-tests/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ export function loggingTransport(_options: BaseTransportOptions): Transport {

/**
* Starts an express server and sends the port to the runner
* @param app Express app
* @param port Port to start the app on. USE WITH CAUTION! By default a random port will be chosen.
* Setting this port to something specific is useful for local debugging but dangerous for
* CI/CD environments where port collisions can cause flakes!
*/
export function startExpressServerAndSendPortToRunner(app: Express): void {
const server = app.listen(0, () => {
export function startExpressServerAndSendPortToRunner(app: Express, port: number | undefined = undefined): void {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small adjustment to our node integration tests framework: In the express apps, we can not optionally pass a fixed port which makes running the app locally a lot less painful 😅

const server = app.listen(port || 0, () => {
const address = server.address() as AddressInfo;

// eslint-disable-next-line no-console
console.log(`{"port":${address.port}}`);
console.log(`{"port":${port || address.port}}`);
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const { loggingTransport } = require('@sentry-internal/node-integration-tests');
const Sentry = require('@sentry/node');

Sentry.init({
dsn: 'https://[email protected]/1337',
release: '1.0',
tracesSampleRate: 1.0,
transport: loggingTransport,

integrations: [
Sentry.httpIntegration({
ignoreIncomingRequests: url => {
return url.includes('/liveness');
},
}),
],
});

// express must be required after Sentry is initialized
const express = require('express');
const cors = require('cors');
const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests');

const app = express();

app.use(cors());

app.get('/test', (_req, res) => {
res.send({ response: 'response 1' });
});

app.get('/liveness', (_req, res) => {
res.send({ response: 'liveness' });
});

Sentry.setupExpressErrorHandler(app);

startExpressServerAndSendPortToRunner(app);
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const { loggingTransport } = require('@sentry-internal/node-integration-tests');
const Sentry = require('@sentry/node');
const http = require('http');

Sentry.init({
dsn: 'https://[email protected]/1337',
release: '1.0',
tracesSampleRate: 1.0,
transport: loggingTransport,

integrations: [
Sentry.httpIntegration({
ignoreOutgoingRequests: url => {
return url.includes('example.com');
},
}),
],
});

// express must be required after Sentry is initialized
const express = require('express');
const cors = require('cors');
const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests');

const app = express();

app.use(cors());

app.get('/test', (_req, response) => {
http
.request('http://example.com/', res => {
res.on('data', () => {});
res.on('end', () => {
response.send({ response: 'done' });
});
})
.end();
});

Sentry.setupExpressErrorHandler(app);

startExpressServerAndSendPortToRunner(app);
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,59 @@ describe('httpIntegration', () => {
.start(done)
.makeRequest('get', '/test');
});

test("doesn't create a root span for incoming requests ignored via `ignoreIncomingRequests`", done => {
const runner = createRunner(__dirname, 'server-ignoreIncomingRequests.js')
.expect({
transaction: {
contexts: {
trace: {
span_id: expect.any(String),
trace_id: expect.any(String),
data: {
url: expect.stringMatching(/\/test$/),
'http.response.status_code': 200,
},
op: 'http.server',
status: 'ok',
},
},
transaction: 'GET /test',
},
})
.start(done);

runner.makeRequest('get', '/liveness'); // should be ignored
runner.makeRequest('get', '/test');
});

test("doesn't create child spans for outgoing requests ignored via `ignoreOutgoingRequests`", done => {
const runner = createRunner(__dirname, 'server-ignoreOutgoingRequests.js')
.expect({
transaction: {
contexts: {
trace: {
span_id: expect.any(String),
trace_id: expect.any(String),
data: {
url: expect.stringMatching(/\/test$/),
'http.response.status_code': 200,
},
op: 'http.server',
status: 'ok',
},
},
transaction: 'GET /test',
spans: [
expect.objectContaining({ op: 'middleware.express', description: 'query' }),
expect.objectContaining({ op: 'middleware.express', description: 'expressInit' }),
expect.objectContaining({ op: 'middleware.express', description: 'corsMiddleware' }),
expect.objectContaining({ op: 'request_handler.express', description: '/test' }),
],
},
})
.start(done);

runner.makeRequest('get', '/test');
});
});
4 changes: 2 additions & 2 deletions packages/node/src/integrations/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export const instrumentHttp = Object.assign(
},

ignoreIncomingRequestHook: request => {
const url = getRequestUrl(request);
const url = request.url;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just checking to be sure, is the url here the full URL, including protocol? so e.g. http://...? As we want these to be full URLs I guess always, if possible.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mydea url is NOT the full url, it is instead just the path (and maybe the query params? EDIT: definitely has query params too.).

the node:IncomingMessage type doesn't have a protocol, hostname, or port attr.

  • For protocol you'd have to do something like access req.socket.encrypted and infer true === https
  • For hostname you'd have to parse the request headers for a Host header, falling back to req.socket.address() if it doesn't exist.
  • For port you'd have to access req.socket.address().port, I think.

So all that to say, sadly it's difficult to reconstruct the full url. Thoughts?

Screenshot:
Screen Shot 2024-07-16 at 5 42 36 AM

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's all the available attrs:
Screen Shot 2024-07-16 at 5 40 06 AM

Copy link

@Bruno-DaSilva Bruno-DaSilva Jul 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mydea sorry i saw you moved your comment, so I just moved mine before I saw you replied 😅
I'll let you move your reply here so the comments aren't fragmented.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I feared that. IMHO this is OK, but we should maybe reflect this in the jsdoc/variables used for ignoreIncomingRequests, e.g. make it ignoreIncomingRequests(urlPath: string) or something like this?

Sounds reasonable to me but sadly this is a breaking change, no?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just changing the name of the variable is fine. Will make the change and update JSDoc and docs to reflect it. "Luckily" this never worked in v8 in the first place (afaict) so we're likely not behaviourally breaking folks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding docs update: I'll do this in combination with the changes from #12930

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good to me 🚀

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: 9348717


const method = request.method?.toUpperCase();
// We do not capture OPTIONS/HEAD requests as transactions
Expand All @@ -112,7 +112,7 @@ export const instrumentHttp = Object.assign(
}

const _ignoreIncomingRequests = _httpOptions.ignoreIncomingRequests;
if (_ignoreIncomingRequests && _ignoreIncomingRequests(url)) {
if (url && _ignoreIncomingRequests && _ignoreIncomingRequests(url)) {
return true;
}

Expand Down
Loading