in lib/instrumentation/http-shared.js [204:378]
exports.traceOutgoingRequest = function (agent, moduleName, method) {
var ins = agent._instrumentation;
return function wrapHttpRequest(orig) {
return function wrappedHttpRequest(input, options, cb) {
const parentRunContext = ins.currRunContext();
var span = ins.createSpan(null, 'external', 'http', { exitSpan: true });
var id = span && span.transaction.id;
agent.logger.debug('intercepted call to %s.%s %o', moduleName, method, {
id,
});
// Reproduce the argument handling from node/lib/_http_client.js#ClientRequest().
//
// The `new URL(...)` calls in this block *could* throw INVALID_URL, but
// that would happen anyway when calling `orig(...)`. The only slight
// downside is that the Error stack won't originate inside "_http_client.js".
if (!nodeHttpRequestSupportsSeparateUrlArg) {
// Signature from node <10.9.0:
// http.request(options[, callback])
// options <Object> | <string> | <URL>
cb = options;
options = input;
if (typeof options === 'string') {
options = safeUrlToHttpOptions(new URL(options));
} else if (options instanceof URL) {
options = safeUrlToHttpOptions(options);
} else {
options = Object.assign({}, options);
}
} else {
// Signature from node >=10.9.0:
// http.request(options[, callback])
// http.request(url[, options][, callback])
// url <string> | <URL>
// options <Object>
if (typeof input === 'string') {
input = safeUrlToHttpOptions(new URL(input));
} else if (input instanceof URL) {
input = safeUrlToHttpOptions(input);
} else {
cb = options;
options = input;
input = null;
}
if (typeof options === 'function') {
cb = options;
options = input || {};
} else {
options = Object.assign(input || {}, options);
}
}
const newArgs = [options];
if (cb !== undefined) {
if (typeof cb === 'function') {
newArgs.push(ins.bindFunctionToRunContext(parentRunContext, cb));
} else {
newArgs.push(cb);
}
}
// W3C trace-context propagation.
// There are a number of reasons why `span` might be null: child of an
// exit span, `transactionMaxSpans` was hit, unsampled transaction, etc.
// If so, then fallback to the current run context's span or transaction,
// if any.
const parent =
span ||
parentRunContext.currSpan() ||
parentRunContext.currTransaction();
if (parent) {
const headers = Object.assign({}, options.headers);
parent.propagateTraceContextHeaders(
headers,
function (carrier, name, value) {
carrier[name] = value;
},
);
options.headers = headers;
}
if (!span) {
return orig.apply(this, newArgs);
}
const spanRunContext = parentRunContext.enterSpan(span);
var req = ins.withRunContext(spanRunContext, orig, this, ...newArgs);
var protocol = req.agent && req.agent.protocol;
agent.logger.debug('request details: %o', {
protocol,
host: getSafeHost(req),
id,
});
ins.bindEmitter(req);
span.action = req.method;
span.name = req.method + ' ' + getSafeHost(req);
// TODO: Research if it's possible to add this to the prototype instead.
// Or if it's somehow preferable to listen for when a `response` listener
// is added instead of when `response` is emitted.
const emit = req.emit;
req.emit = function wrappedEmit(type, res) {
if (type === 'response') onResponse(res);
if (type === 'abort') onAbort(type);
return emit.apply(req, arguments);
};
const url = getUrlFromRequestAndOptions(req, options, moduleName + ':');
if (!url) {
agent.logger.warn('unable to identify http.ClientRequest url %o', {
id,
});
}
let statusCode;
return req;
// In case the request is ended prematurely
function onAbort(type) {
if (span.ended) return;
agent.logger.debug('intercepted http.ClientRequest abort event %o', {
id,
});
onEnd();
}
function onEnd() {
span.setHttpContext({
method: req.method,
status_code: statusCode,
url,
});
// Add destination info only when socket conn is established
if (url) {
// The `getHTTPDestination` function might throw in case an
// invalid URL is given to the `URL()` function. Until we can
// be 100% sure this doesn't happen, we better catch it here.
// For details, see:
// https://github.com/elastic/apm-agent-nodejs/issues/1769
try {
span._setDestinationContext(getHTTPDestination(url));
} catch (e) {
agent.logger.error(
'Could not set destination context: %s',
e.message,
);
}
}
span._setOutcomeFromHttpStatusCode(statusCode);
span.end();
}
function onResponse(res) {
agent.logger.debug('intercepted http.ClientRequest response event %o', {
id,
});
ins.bindEmitterToRunContext(parentRunContext, res);
statusCode = res.statusCode;
res.prependListener('end', function () {
agent.logger.debug('intercepted http.IncomingMessage end event %o', {
id,
});
onEnd();
});
}
};
};
};