Skip to content

Commit

Permalink
feat(node): Expose getHttpInstrumentationOptions utility
Browse files Browse the repository at this point in the history
  • Loading branch information
mydea committed Sep 18, 2024
1 parent 03eb680 commit 6dcc5be
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 100 deletions.
2 changes: 1 addition & 1 deletion packages/node/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { httpIntegration } from './integrations/http';
export { httpIntegration, getHttpInstrumentationOptions } from './integrations/http';
export { nativeNodeFetchIntegration } from './integrations/node-fetch';
export { fsIntegration } from './integrations/fs';

Expand Down
209 changes: 110 additions & 99 deletions packages/node/src/integrations/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,105 +94,7 @@ export const instrumentHttp = Object.assign(

_httpInstrumentation = new _InstrumentationClass({
..._httpOptions.instrumentation?._experimentalConfig,
ignoreOutgoingRequestHook: request => {
const url = getRequestUrl(request);

if (!url) {
return false;
}

const _ignoreOutgoingRequests = _httpOptions.ignoreOutgoingRequests;
if (_ignoreOutgoingRequests && _ignoreOutgoingRequests(url, request)) {
return true;
}

return false;
},

ignoreIncomingRequestHook: request => {
// request.url is the only property that holds any information about the url
// it only consists of the URL path and query string (if any)
const urlPath = request.url;

const method = request.method?.toUpperCase();
// We do not capture OPTIONS/HEAD requests as transactions
if (method === 'OPTIONS' || method === 'HEAD') {
return true;
}

const _ignoreIncomingRequests = _httpOptions.ignoreIncomingRequests;
if (urlPath && _ignoreIncomingRequests && _ignoreIncomingRequests(urlPath, request)) {
return true;
}

return false;
},

requireParentforOutgoingSpans: false,
requireParentforIncomingSpans: false,
requestHook: (span, req) => {
addOriginToSpan(span, 'auto.http.otel.http');

// both, incoming requests and "client" requests made within the app trigger the requestHook
// we only want to isolate and further annotate incoming requests (IncomingMessage)
if (_isClientRequest(req)) {
_httpOptions.instrumentation?.requestHook?.(span, req);
return;
}

const scopes = getCapturedScopesOnSpan(span);

const isolationScope = (scopes.isolationScope || getIsolationScope()).clone();
const scope = scopes.scope || getCurrentScope();

// Update the isolation scope, isolate this request
isolationScope.setSDKProcessingMetadata({ request: req });

const client = getClient<NodeClient>();
if (client && client.getOptions().autoSessionTracking) {
isolationScope.setRequestSession({ status: 'ok' });
}
setIsolationScope(isolationScope);
setCapturedScopesOnSpan(span, scope, isolationScope);

// attempt to update the scope's `transactionName` based on the request URL
// Ideally, framework instrumentations coming after the HttpInstrumentation
// update the transactionName once we get a parameterized route.
const httpMethod = (req.method || 'GET').toUpperCase();
const httpTarget = stripUrlQueryAndFragment(req.url || '/');

const bestEffortTransactionName = `${httpMethod} ${httpTarget}`;

isolationScope.setTransactionName(bestEffortTransactionName);

if (isKnownPrefetchRequest(req)) {
span.setAttribute('sentry.http.prefetch', true);
}

_httpOptions.instrumentation?.requestHook?.(span, req);
},
responseHook: (span, res) => {
const client = getClient<NodeClient>();
if (client && client.getOptions().autoSessionTracking) {
setImmediate(() => {
client['_captureRequestSession']();
});
}

_httpOptions.instrumentation?.responseHook?.(span, res);
},
applyCustomAttributesOnSpan: (
span: Span,
request: ClientRequest | HTTPModuleRequestIncomingMessage,
response: HTTPModuleRequestIncomingMessage | ServerResponse,
) => {
const _breadcrumbs = typeof _httpOptions.breadcrumbs === 'undefined' ? true : _httpOptions.breadcrumbs;
if (_breadcrumbs) {
_addRequestBreadcrumb(request, response);
}

_httpOptions.instrumentation?.applyCustomAttributesOnSpan?.(span, request, response);
},
...getHttpInstrumentationOptions(),
});

addOpenTelemetryInstrumentation(_httpInstrumentation);
Expand All @@ -218,6 +120,115 @@ const _httpIntegration = ((options: HttpOptions = {}) => {
*/
export const httpIntegration = defineIntegration(_httpIntegration);

/**
* Get the options to be passed to the HTTP instrumentation.
* This includes all the things you need to make the Sentry SDK work,
* especially `requestHook` is necessary to be passed.
*/
export function getHttpInstrumentationOptions(): ConstructorParameters<typeof HttpInstrumentation>[0] {
return {
ignoreOutgoingRequestHook: request => {
const url = getRequestUrl(request);

if (!url) {
return false;
}

const _ignoreOutgoingRequests = _httpOptions.ignoreOutgoingRequests;
if (_ignoreOutgoingRequests && _ignoreOutgoingRequests(url, request)) {
return true;
}

return false;
},

ignoreIncomingRequestHook: request => {
// request.url is the only property that holds any information about the url
// it only consists of the URL path and query string (if any)
const urlPath = request.url;

const method = request.method?.toUpperCase();
// We do not capture OPTIONS/HEAD requests as transactions
if (method === 'OPTIONS' || method === 'HEAD') {
return true;
}

const _ignoreIncomingRequests = _httpOptions.ignoreIncomingRequests;
if (urlPath && _ignoreIncomingRequests && _ignoreIncomingRequests(urlPath, request)) {
return true;
}

return false;
},

requireParentforOutgoingSpans: false,
requireParentforIncomingSpans: false,
requestHook: (span, req) => {
addOriginToSpan(span, 'auto.http.otel.http');

// both, incoming requests and "client" requests made within the app trigger the requestHook
// we only want to isolate and further annotate incoming requests (IncomingMessage)
if (_isClientRequest(req)) {
_httpOptions.instrumentation?.requestHook?.(span, req);
return;
}

const scopes = getCapturedScopesOnSpan(span);

const isolationScope = (scopes.isolationScope || getIsolationScope()).clone();
const scope = scopes.scope || getCurrentScope();

// Update the isolation scope, isolate this request
isolationScope.setSDKProcessingMetadata({ request: req });

const client = getClient<NodeClient>();
if (client && client.getOptions().autoSessionTracking) {
isolationScope.setRequestSession({ status: 'ok' });
}
setIsolationScope(isolationScope);
setCapturedScopesOnSpan(span, scope, isolationScope);

// attempt to update the scope's `transactionName` based on the request URL
// Ideally, framework instrumentations coming after the HttpInstrumentation
// update the transactionName once we get a parameterized route.
const httpMethod = (req.method || 'GET').toUpperCase();
const httpTarget = stripUrlQueryAndFragment(req.url || '/');

const bestEffortTransactionName = `${httpMethod} ${httpTarget}`;

isolationScope.setTransactionName(bestEffortTransactionName);

if (isKnownPrefetchRequest(req)) {
span.setAttribute('sentry.http.prefetch', true);
}

_httpOptions.instrumentation?.requestHook?.(span, req);
},
responseHook: (span, res) => {
const client = getClient<NodeClient>();
if (client && client.getOptions().autoSessionTracking) {
setImmediate(() => {
client['_captureRequestSession']();
});
}

_httpOptions.instrumentation?.responseHook?.(span, res);
},
applyCustomAttributesOnSpan: (
span: Span,
request: ClientRequest | HTTPModuleRequestIncomingMessage,
response: HTTPModuleRequestIncomingMessage | ServerResponse,
) => {
const _breadcrumbs = typeof _httpOptions.breadcrumbs === 'undefined' ? true : _httpOptions.breadcrumbs;
if (_breadcrumbs) {
_addRequestBreadcrumb(request, response);
}

_httpOptions.instrumentation?.applyCustomAttributesOnSpan?.(span, request, response);
},
};
}

/** Add a breadcrumb for outgoing requests. */
function _addRequestBreadcrumb(
request: ClientRequest | HTTPModuleRequestIncomingMessage,
Expand Down

0 comments on commit 6dcc5be

Please sign in to comment.