-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
ref(node)!: Remove legacy incoming HTTP span hooks and default keepAlive to true #23396
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
Changes from all commits
730a041
bcbc984
044bf40
00722fe
8e48326
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -141,7 +141,8 @@ interface HttpOptions { | |
| disableIncomingRequestSpans?: boolean; | ||
|
|
||
| /** | ||
| * Additional instrumentation options that are passed to the underlying HttpInstrumentation. | ||
| * Hooks for outgoing HTTP request spans. | ||
| * These no longer run for incoming request spans; use `incomingRequestSpanHook` for those. | ||
| */ | ||
| instrumentation?: { | ||
| requestHook?: (span: Span, req: HttpIncomingMessage | HttpClientRequest) => void; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. low/cleanup: These are now only for outgoing requests now, but the types still accept
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Already handled in the stacked PR |
||
|
|
@@ -174,8 +175,6 @@ export const httpIntegration = defineIntegration((options: HttpOptions = {}) => | |
| ignoreIncomingRequests: options.ignoreIncomingRequests, | ||
| ignoreStaticAssets: options.ignoreStaticAssets, | ||
| ignoreStatusCodes: options.dropSpansForIncomingRequestStatusCodes, | ||
| // oxlint-disable-next-line typescript/no-deprecated -- pass through the deprecated option for back-compat | ||
| instrumentation: options.instrumentation, | ||
| onSpanCreated: options.incomingRequestSpanHook, | ||
| }; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,7 +20,7 @@ export interface NodeTransportOptions extends BaseTransportOptions { | |
| caCerts?: string | Buffer | Array<string | Buffer>; | ||
| /** Custom HTTP module. Defaults to the native 'http' and 'https' modules. */ | ||
| httpModule?: HTTPModule; | ||
| /** Allow overriding connection keepAlive, defaults to false */ | ||
| /** Allow overriding connection keepAlive, defaults to true */ | ||
| keepAlive?: boolean; | ||
| } | ||
|
|
||
|
|
@@ -68,10 +68,7 @@ export function makeNodeTransport(options: NodeTransportOptions): Transport { | |
| ); | ||
|
|
||
| const nativeHttpModule = isHttps ? https : http; | ||
| const keepAlive = options.keepAlive === undefined ? false : options.keepAlive; | ||
|
|
||
| // TODO(v11): Evaluate if we can set keepAlive to true. This would involve testing for memory leaks in older node | ||
| // versions(>= 8) as they had memory leaks when using it: #2555 | ||
| const keepAlive = options.keepAlive ?? true; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The node 8 memory leaks are not an issue, but I think this might have some further reaching consequences, since this transport is also used for aws-serverless and google-cloud-serverless use, so it's possible that the socket times out while the process is frozen. (Also, this is ignored for the proxy case.) It's not going to fail often, but I think we'd need to add a retry in makeRequest to make sure that if we get a dead socket, we don't crash on it. Eg, wrap the contents of req.on('error', error => {
if (canRetry && req.reusedSocket && (error as { code?: string }).code === 'ECONNRESET') {
resolve(sendRequest(false));
} else {
reject(error);
}
});
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice catch! will add the keepAlive reused-socket retry |
||
| const agent = proxy | ||
| ? (new HttpsProxyAgent(proxy) as http.Agent) | ||
| : new nativeHttpModule.Agent({ keepAlive, maxSockets: 30, timeout: 2000 }); | ||
|
|
@@ -113,63 +110,75 @@ function createRequestExecutor( | |
| ): TransportRequestExecutor { | ||
| const { hostname, pathname, port, protocol, search } = new URL(options.url); | ||
| return function makeRequest(request: TransportRequest): Promise<TransportMakeRequestResponse> { | ||
| return new Promise((resolve, reject) => { | ||
| // This ensures we do not generate any spans in OpenTelemetry for the transport | ||
| suppressTracing(() => { | ||
| let body = streamFromBody(request.body); | ||
|
|
||
| const headers: Record<string, string> = { ...options.headers }; | ||
|
|
||
| if (request.body.length > GZIP_THRESHOLD) { | ||
| headers['content-encoding'] = 'gzip'; | ||
| body = body.pipe(createGzip()); | ||
| } | ||
|
|
||
| const hostnameIsIPv6 = hostname.startsWith('['); | ||
|
|
||
| const req = httpModule.request( | ||
| { | ||
| method: 'POST', | ||
| agent, | ||
| headers, | ||
| // Remove "[" and "]" from IPv6 hostnames | ||
| hostname: hostnameIsIPv6 ? hostname.slice(1, -1) : hostname, | ||
| path: `${pathname}${search}`, | ||
| port, | ||
| protocol, | ||
| ca: options.caCerts, | ||
| }, | ||
| res => { | ||
| res.on('data', () => { | ||
| // Drain socket | ||
| }); | ||
|
|
||
| res.on('end', () => { | ||
| // Drain socket | ||
| }); | ||
|
|
||
| res.setEncoding('utf8'); | ||
|
|
||
| // "Key-value pairs of header names and values. Header names are lower-cased." | ||
| // https://nodejs.org/api/http.html#http_message_headers | ||
| const retryAfterHeader = res.headers['retry-after'] ?? null; | ||
| const rateLimitsHeader = res.headers['x-sentry-rate-limits'] ?? null; | ||
|
|
||
| resolve({ | ||
| statusCode: res.statusCode, | ||
| headers: { | ||
| 'retry-after': retryAfterHeader, | ||
| 'x-sentry-rate-limits': Array.isArray(rateLimitsHeader) | ||
| ? rateLimitsHeader[0] || null | ||
| : rateLimitsHeader, | ||
| }, | ||
| }); | ||
| }, | ||
| ); | ||
|
|
||
| req.on('error', reject); | ||
| body.pipe(req); | ||
| const sendRequest = (canRetry: boolean): Promise<TransportMakeRequestResponse> => | ||
| new Promise((resolve, reject) => { | ||
| // This ensures we do not generate any spans in OpenTelemetry for the transport | ||
| suppressTracing(() => { | ||
| // Recreate the body on each attempt so a retry is not piping a consumed stream. | ||
| let body = streamFromBody(request.body); | ||
|
|
||
| const headers: Record<string, string> = { ...options.headers }; | ||
|
|
||
| if (request.body.length > GZIP_THRESHOLD) { | ||
| headers['content-encoding'] = 'gzip'; | ||
| body = body.pipe(createGzip()); | ||
| } | ||
|
|
||
| const hostnameIsIPv6 = hostname.startsWith('['); | ||
|
|
||
| const req = httpModule.request( | ||
| { | ||
| method: 'POST', | ||
| agent, | ||
| headers, | ||
| // Remove "[" and "]" from IPv6 hostnames | ||
| hostname: hostnameIsIPv6 ? hostname.slice(1, -1) : hostname, | ||
| path: `${pathname}${search}`, | ||
| port, | ||
| protocol, | ||
| ca: options.caCerts, | ||
| }, | ||
| res => { | ||
| res.on('data', () => { | ||
| // Drain socket | ||
| }); | ||
|
|
||
| res.on('end', () => { | ||
| // Drain socket | ||
| }); | ||
|
|
||
| res.setEncoding('utf8'); | ||
|
|
||
| // "Key-value pairs of header names and values. Header names are lower-cased." | ||
| // https://nodejs.org/api/http.html#http_message_headers | ||
| const retryAfterHeader = res.headers['retry-after'] ?? null; | ||
| const rateLimitsHeader = res.headers['x-sentry-rate-limits'] ?? null; | ||
|
|
||
| resolve({ | ||
| statusCode: res.statusCode, | ||
| headers: { | ||
| 'retry-after': retryAfterHeader, | ||
| 'x-sentry-rate-limits': Array.isArray(rateLimitsHeader) | ||
| ? rateLimitsHeader[0] || null | ||
| : rateLimitsHeader, | ||
| }, | ||
| }); | ||
| }, | ||
| ); | ||
|
|
||
| req.on('error', error => { | ||
| // Keep-alive sockets can go stale while a serverless isolate is frozen. | ||
| // Node recommends a single retry when the reused socket resets. | ||
| if (canRetry && req.reusedSocket && (error as NodeJS.ErrnoException).code === 'ECONNRESET') { | ||
| resolve(sendRequest(false)); | ||
| } else { | ||
| reject(error); | ||
| } | ||
| }); | ||
| body.pipe(req); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| return sendRequest(true); | ||
| }; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.