Skip to content

Commit 306a2d2

Browse files
authored
Merge branch 'develop' into sig/nuxt-plugin-template
2 parents 09e5255 + a2dcb28 commit 306a2d2

File tree

8 files changed

+212
-29
lines changed

8 files changed

+212
-29
lines changed

.github/workflows/external-contributors.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ on:
99
jobs:
1010
external_contributor:
1111
name: External Contributors
12+
permissions:
13+
pull-requests: write
14+
contents: write
1215
runs-on: ubuntu-20.04
1316
if: |
17+
github.event.pull_request.merged == true &&
1418
github.event.pull_request.author_association != 'COLLABORATOR'
1519
&& github.event.pull_request.author_association != 'MEMBER'
1620
&& github.event.pull_request.author_association != 'OWNER'

dev-packages/node-integration-tests/suites/express/tracing/test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';
22

3-
describe('express tracing experimental', () => {
3+
describe('express tracing', () => {
44
afterAll(() => {
55
cleanupChildProcesses();
66
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const { loggingTransport } = require('@sentry-internal/node-integration-tests');
2+
const Sentry = require('@sentry/node');
3+
4+
Sentry.init({
5+
dsn: 'https://[email protected]/1337',
6+
release: '1.0',
7+
// disable attaching headers to /test/* endpoints
8+
tracePropagationTargets: [/^(?!.*test).*$/],
9+
tracesSampleRate: 1.0,
10+
transport: loggingTransport,
11+
12+
integrations: [
13+
Sentry.httpIntegration({
14+
instrumentation: {
15+
_experimentalConfig: {
16+
serverName: 'sentry-test-server-name',
17+
},
18+
},
19+
}),
20+
],
21+
});
22+
23+
// express must be required after Sentry is initialized
24+
const express = require('express');
25+
const cors = require('cors');
26+
const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests');
27+
28+
const app = express();
29+
30+
app.use(cors());
31+
32+
app.get('/test', (_req, res) => {
33+
res.send({ response: 'response 1' });
34+
});
35+
36+
Sentry.setupExpressErrorHandler(app);
37+
38+
startExpressServerAndSendPortToRunner(app);
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
const { loggingTransport } = require('@sentry-internal/node-integration-tests');
2+
const Sentry = require('@sentry/node');
3+
4+
Sentry.init({
5+
dsn: 'https://[email protected]/1337',
6+
release: '1.0',
7+
// disable attaching headers to /test/* endpoints
8+
tracePropagationTargets: [/^(?!.*test).*$/],
9+
tracesSampleRate: 1.0,
10+
transport: loggingTransport,
11+
12+
integrations: [
13+
Sentry.httpIntegration({
14+
instrumentation: {
15+
requestHook: (span, req) => {
16+
span.setAttribute('attr1', 'yes');
17+
Sentry.setExtra('requestHookCalled', {
18+
url: req.url,
19+
method: req.method,
20+
});
21+
},
22+
responseHook: (span, res) => {
23+
span.setAttribute('attr2', 'yes');
24+
Sentry.setExtra('responseHookCalled', {
25+
url: res.req.url,
26+
method: res.req.method,
27+
});
28+
},
29+
applyCustomAttributesOnSpan: (span, req, res) => {
30+
span.setAttribute('attr3', 'yes');
31+
Sentry.setExtra('applyCustomAttributesOnSpanCalled', {
32+
reqUrl: req.url,
33+
reqMethod: req.method,
34+
resUrl: res.req.url,
35+
resMethod: res.req.method,
36+
});
37+
},
38+
},
39+
}),
40+
],
41+
});
42+
43+
// express must be required after Sentry is initialized
44+
const express = require('express');
45+
const cors = require('cors');
46+
const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests');
47+
48+
const app = express();
49+
50+
app.use(cors());
51+
52+
app.get('/test', (_req, res) => {
53+
res.send({ response: 'response 1' });
54+
});
55+
56+
Sentry.setupExpressErrorHandler(app);
57+
58+
startExpressServerAndSendPortToRunner(app);
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';
2+
3+
describe('httpIntegration', () => {
4+
afterAll(() => {
5+
cleanupChildProcesses();
6+
});
7+
8+
test('allows to pass instrumentation options to integration', done => {
9+
// response shape seems different on Node 14, so we skip this there
10+
const nodeMajorVersion = Number(process.versions.node.split('.')[0]);
11+
if (nodeMajorVersion <= 14) {
12+
done();
13+
return;
14+
}
15+
16+
createRunner(__dirname, 'server.js')
17+
.ignore('session', 'sessions')
18+
.expect({
19+
transaction: {
20+
contexts: {
21+
trace: {
22+
span_id: expect.any(String),
23+
trace_id: expect.any(String),
24+
data: {
25+
url: expect.stringMatching(/\/test$/),
26+
'http.response.status_code': 200,
27+
attr1: 'yes',
28+
attr2: 'yes',
29+
attr3: 'yes',
30+
},
31+
op: 'http.server',
32+
status: 'ok',
33+
},
34+
},
35+
extra: {
36+
requestHookCalled: {
37+
url: expect.stringMatching(/\/test$/),
38+
method: 'GET',
39+
},
40+
responseHookCalled: {
41+
url: expect.stringMatching(/\/test$/),
42+
method: 'GET',
43+
},
44+
applyCustomAttributesOnSpanCalled: {
45+
reqUrl: expect.stringMatching(/\/test$/),
46+
reqMethod: 'GET',
47+
resUrl: expect.stringMatching(/\/test$/),
48+
resMethod: 'GET',
49+
},
50+
},
51+
},
52+
})
53+
.start(done)
54+
.makeRequest('get', '/test');
55+
});
56+
57+
test('allows to pass experimental config through to integration', done => {
58+
createRunner(__dirname, 'server-experimental.js')
59+
.ignore('session', 'sessions')
60+
.expect({
61+
transaction: {
62+
contexts: {
63+
trace: {
64+
span_id: expect.any(String),
65+
trace_id: expect.any(String),
66+
data: {
67+
url: expect.stringMatching(/\/test$/),
68+
'http.response.status_code': 200,
69+
'http.server_name': 'sentry-test-server-name',
70+
},
71+
op: 'http.server',
72+
status: 'ok',
73+
},
74+
},
75+
},
76+
})
77+
.start(done)
78+
.makeRequest('get', '/test');
79+
});
80+
});

packages/nextjs/src/server/httpIntegration.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,7 @@ class CustomNextjsHttpIntegration extends HttpInstrumentation {
2222
}
2323
}
2424

25-
interface HttpOptions {
26-
/**
27-
* Whether breadcrumbs should be recorded for requests.
28-
* Defaults to true
29-
*/
30-
breadcrumbs?: boolean;
31-
32-
/**
33-
* Do not capture spans or breadcrumbs for outgoing HTTP requests to URLs where the given callback returns `true`.
34-
* This controls both span & breadcrumb creation - spans will be non recording if tracing is disabled.
35-
*/
36-
ignoreOutgoingRequests?: (url: string) => boolean;
37-
}
25+
type HttpOptions = Parameters<typeof originalHttpIntegration>[0];
3826

3927
/**
4028
* The http integration instruments Node's internal http and https modules.

packages/node/src/integrations/http.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,25 @@ interface HttpOptions {
4343
*/
4444
ignoreIncomingRequests?: (url: string) => boolean;
4545

46+
/**
47+
* Additional instrumentation options that are passed to the underlying HttpInstrumentation.
48+
*/
49+
instrumentation?: {
50+
requestHook?: (span: Span, req: ClientRequest | HTTPModuleRequestIncomingMessage) => void;
51+
responseHook?: (span: Span, response: HTTPModuleRequestIncomingMessage | ServerResponse) => void;
52+
applyCustomAttributesOnSpan?: (
53+
span: Span,
54+
request: ClientRequest | HTTPModuleRequestIncomingMessage,
55+
response: HTTPModuleRequestIncomingMessage | ServerResponse,
56+
) => void;
57+
58+
/**
59+
* You can pass any configuration through to the underlying instrumention.
60+
* Note that there are no semver guarantees for this!
61+
*/
62+
_experimentalConfig?: ConstructorParameters<typeof HttpInstrumentation>[0];
63+
};
64+
4665
/** Allows to pass a custom version of HttpInstrumentation. We use this for Next.js. */
4766
_instrumentation?: typeof HttpInstrumentation;
4867
}
@@ -63,6 +82,7 @@ export const instrumentHttp = Object.assign(
6382
const _InstrumentationClass = _httpOptions._instrumentation || HttpInstrumentation;
6483

6584
_httpInstrumentation = new _InstrumentationClass({
85+
..._httpOptions.instrumentation?._experimentalConfig,
6686
ignoreOutgoingRequestHook: request => {
6787
const url = getRequestUrl(request);
6888

@@ -107,6 +127,7 @@ export const instrumentHttp = Object.assign(
107127
// both, incoming requests and "client" requests made within the app trigger the requestHook
108128
// we only want to isolate and further annotate incoming requests (IncomingMessage)
109129
if (_isClientRequest(req)) {
130+
_httpOptions.instrumentation?.requestHook?.(span, req);
110131
return;
111132
}
112133

@@ -134,24 +155,30 @@ export const instrumentHttp = Object.assign(
134155
const bestEffortTransactionName = `${httpMethod} ${httpTarget}`;
135156

136157
isolationScope.setTransactionName(bestEffortTransactionName);
158+
159+
_httpOptions.instrumentation?.requestHook?.(span, req);
137160
},
138-
responseHook: () => {
161+
responseHook: (span, res) => {
139162
const client = getClient<NodeClient>();
140163
if (client && client.getOptions().autoSessionTracking) {
141164
setImmediate(() => {
142165
client['_captureRequestSession']();
143166
});
144167
}
168+
169+
_httpOptions.instrumentation?.responseHook?.(span, res);
145170
},
146171
applyCustomAttributesOnSpan: (
147-
_span: Span,
172+
span: Span,
148173
request: ClientRequest | HTTPModuleRequestIncomingMessage,
149174
response: HTTPModuleRequestIncomingMessage | ServerResponse,
150175
) => {
151176
const _breadcrumbs = typeof _httpOptions.breadcrumbs === 'undefined' ? true : _httpOptions.breadcrumbs;
152177
if (_breadcrumbs) {
153178
_addRequestBreadcrumb(request, response);
154179
}
180+
181+
_httpOptions.instrumentation?.applyCustomAttributesOnSpan?.(span, request, response);
155182
},
156183
});
157184

packages/remix/src/utils/integrations/http.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,7 @@ class RemixHttpIntegration extends HttpInstrumentation {
1616
}
1717
}
1818

19-
interface HttpOptions {
20-
/**
21-
* Whether breadcrumbs should be recorded for requests.
22-
* Defaults to true
23-
*/
24-
breadcrumbs?: boolean;
25-
26-
/**
27-
* Do not capture spans or breadcrumbs for outgoing HTTP requests to URLs where the given callback returns `true`.
28-
* This controls both span & breadcrumb creation - spans will be non recording if tracing is disabled.
29-
*/
30-
ignoreOutgoingRequests?: (url: string) => boolean;
31-
}
19+
type HttpOptions = Parameters<typeof originalHttpIntegration>[0];
3220

3321
/**
3422
* The http integration instruments Node's internal http and https modules.

0 commit comments

Comments
 (0)