@sentry/node
Version:
Official Sentry SDK for Node.js
289 lines (242 loc) • 8.44 kB
JavaScript
var {
_optionalChain
} = require('@sentry/utils/cjs/buildPolyfills');
Object.defineProperty(exports, '__esModule', { value: true });
const core = require('@sentry/core');
const utils = require('@sentry/utils');
const lru_map = require('lru_map');
const nodeVersion = require('../../nodeVersion.js');
const http = require('../utils/http.js');
exports.ChannelName = void 0;(function (ChannelName) {
// https://github.com/nodejs/undici/blob/e6fc80f809d1217814c044f52ed40ef13f21e43c/docs/api/DiagnosticsChannel.md#undicirequestcreate
const RequestCreate = 'undici:request:create'; ChannelName["RequestCreate"] = RequestCreate;
const RequestEnd = 'undici:request:headers'; ChannelName["RequestEnd"] = RequestEnd;
const RequestError = 'undici:request:error'; ChannelName["RequestError"] = RequestError;
})(exports.ChannelName || (exports.ChannelName = {}));
// Please note that you cannot use `console.log` to debug the callbacks registered to the `diagnostics_channel` API.
// To debug, you can use `writeFileSync` to write to a file:
// https://nodejs.org/api/async_hooks.html#printing-in-asynchook-callbacks
//
// import { writeFileSync } from 'fs';
// import { format } from 'util';
//
// function debug(...args: any): void {
// // Use a function like this one when debugging inside an AsyncHook callback
// // @ts-expect-error any
// writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' });
// }
/**
* Instruments outgoing HTTP requests made with the `undici` package via
* Node's `diagnostics_channel` API.
*
* Supports Undici 4.7.0 or higher.
*
* Requires Node 16.17.0 or higher.
*/
class Undici {
/**
* @inheritDoc
*/
static __initStatic() {this.id = 'Undici';}
/**
* @inheritDoc
*/
__init() {this.name = Undici.id;}
__init2() {this._createSpanUrlMap = new lru_map.LRUMap(100);}
__init3() {this._headersUrlMap = new lru_map.LRUMap(100);}
constructor(_options = {}) {Undici.prototype.__init.call(this);Undici.prototype.__init2.call(this);Undici.prototype.__init3.call(this);Undici.prototype.__init4.call(this);Undici.prototype.__init5.call(this);Undici.prototype.__init6.call(this);
this._options = {
breadcrumbs: _options.breadcrumbs === undefined ? true : _options.breadcrumbs,
shouldCreateSpanForRequest: _options.shouldCreateSpanForRequest,
};
}
/**
* @inheritDoc
*/
setupOnce(_addGlobalEventProcessor) {
// Requires Node 16+ to use the diagnostics_channel API.
if (nodeVersion.NODE_VERSION.major && nodeVersion.NODE_VERSION.major < 16) {
return;
}
let ds;
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
ds = utils.dynamicRequire(module, 'diagnostics_channel') ;
} catch (e) {
// no-op
}
if (!ds || !ds.subscribe) {
return;
}
// https://github.com/nodejs/undici/blob/e6fc80f809d1217814c044f52ed40ef13f21e43c/docs/api/DiagnosticsChannel.md
ds.subscribe(exports.ChannelName.RequestCreate, this._onRequestCreate);
ds.subscribe(exports.ChannelName.RequestEnd, this._onRequestEnd);
ds.subscribe(exports.ChannelName.RequestError, this._onRequestError);
}
/** Helper that wraps shouldCreateSpanForRequest option */
_shouldCreateSpan(url) {
if (this._options.shouldCreateSpanForRequest === undefined) {
return true;
}
const cachedDecision = this._createSpanUrlMap.get(url);
if (cachedDecision !== undefined) {
return cachedDecision;
}
const decision = this._options.shouldCreateSpanForRequest(url);
this._createSpanUrlMap.set(url, decision);
return decision;
}
__init4() {this._onRequestCreate = (message) => {
const hub = core.getCurrentHub();
if (!hub.getIntegration(Undici)) {
return;
}
const { request } = message ;
const stringUrl = request.origin ? request.origin.toString() + request.path : request.path;
if (http.isSentryRequest(stringUrl) || request.__sentry_span__ !== undefined) {
return;
}
const client = hub.getClient();
if (!client) {
return;
}
const clientOptions = client.getOptions();
const scope = hub.getScope();
const parentSpan = scope.getSpan();
const span = this._shouldCreateSpan(stringUrl) ? createRequestSpan(parentSpan, request, stringUrl) : undefined;
if (span) {
request.__sentry_span__ = span;
}
const shouldAttachTraceData = (url) => {
if (clientOptions.tracePropagationTargets === undefined) {
return true;
}
const cachedDecision = this._headersUrlMap.get(url);
if (cachedDecision !== undefined) {
return cachedDecision;
}
const decision = utils.stringMatchesSomePattern(url, clientOptions.tracePropagationTargets);
this._headersUrlMap.set(url, decision);
return decision;
};
if (shouldAttachTraceData(stringUrl)) {
if (span) {
const dynamicSamplingContext = _optionalChain([span, 'optionalAccess', _4 => _4.transaction, 'optionalAccess', _5 => _5.getDynamicSamplingContext, 'call', _6 => _6()]);
const sentryBaggageHeader = utils.dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
setHeadersOnRequest(request, span.toTraceparent(), sentryBaggageHeader);
} else {
const { traceId, sampled, dsc } = scope.getPropagationContext();
const sentryTrace = utils.generateSentryTraceHeader(traceId, undefined, sampled);
const dynamicSamplingContext = dsc || core.getDynamicSamplingContextFromClient(traceId, client, scope);
const sentryBaggageHeader = utils.dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
setHeadersOnRequest(request, sentryTrace, sentryBaggageHeader);
}
}
};}
__init5() {this._onRequestEnd = (message) => {
const hub = core.getCurrentHub();
if (!hub.getIntegration(Undici)) {
return;
}
const { request, response } = message ;
const stringUrl = request.origin ? request.origin.toString() + request.path : request.path;
if (http.isSentryRequest(stringUrl)) {
return;
}
const span = request.__sentry_span__;
if (span) {
span.setHttpStatus(response.statusCode);
span.finish();
}
if (this._options.breadcrumbs) {
hub.addBreadcrumb(
{
category: 'http',
data: {
method: request.method,
status_code: response.statusCode,
url: stringUrl,
},
type: 'http',
},
{
event: 'response',
request,
response,
},
);
}
};}
__init6() {this._onRequestError = (message) => {
const hub = core.getCurrentHub();
if (!hub.getIntegration(Undici)) {
return;
}
const { request } = message ;
const stringUrl = request.origin ? request.origin.toString() + request.path : request.path;
if (http.isSentryRequest(stringUrl)) {
return;
}
const span = request.__sentry_span__;
if (span) {
span.setStatus('internal_error');
span.finish();
}
if (this._options.breadcrumbs) {
hub.addBreadcrumb(
{
category: 'http',
data: {
method: request.method,
url: stringUrl,
},
level: 'error',
type: 'http',
},
{
event: 'error',
request,
},
);
}
};}
}Undici.__initStatic();
function setHeadersOnRequest(
request,
sentryTrace,
sentryBaggageHeader,
) {
if (request.__sentry_has_headers__) {
return;
}
request.addHeader('sentry-trace', sentryTrace);
if (sentryBaggageHeader) {
request.addHeader('baggage', sentryBaggageHeader);
}
request.__sentry_has_headers__ = true;
}
function createRequestSpan(
activeSpan,
request,
stringUrl,
) {
const url = utils.parseUrl(stringUrl);
const method = request.method || 'GET';
const data = {
'http.method': method,
};
if (url.search) {
data['http.query'] = url.search;
}
if (url.hash) {
data['http.fragment'] = url.hash;
}
return _optionalChain([activeSpan, 'optionalAccess', _7 => _7.startChild, 'call', _8 => _8({
op: 'http.client',
origin: 'auto.http.node.undici',
description: `${method} ${utils.getSanitizedUrlString(url)}`,
data,
})]);
}
exports.Undici = Undici;
//# sourceMappingURL=index.js.map