in lib/instrumentation/modules/@elastic/elasticsearch.js [130:342]
function wrapRequest(origRequest) {
return function wrappedRequest(params, options, cb) {
options = options || {};
if (typeof options === 'function') {
cb = options;
options = {};
}
if (typeof cb !== 'function' && doubleCallsRequestIfNoCb) {
return origRequest.apply(this, arguments);
}
const method = (params && params.method) || '<UnknownMethod>';
const path = (params && params.path) || '<UnknownPath>';
agent.logger.debug(
{ method, path },
'intercepted call to @elastic/elasticsearch.Transport.prototype.request',
);
const span = ins.createSpan(
`Elasticsearch: ${method} ${path}`,
'db',
'elasticsearch',
'request',
{ exitSpan: true },
);
if (!span) {
return origRequest.apply(this, arguments);
}
const parentRunContext = ins.currRunContext();
const spanRunContext = parentRunContext.enterSpan(span);
const finish = ins.bindFunctionToRunContext(
spanRunContext,
(err, result) => {
// Set destination context.
// Use the connection from wrappedGetConnection() above, if that worked.
// Otherwise, fallback to using the first connection on
// `Transport#connectionPool`, if any. (This is the best parsed
// representation of connection options passed to the Client ctor.)
let conn = connFromSpan.get(span);
if (conn) {
connFromSpan.delete(span);
} else if (this.connectionPool && this.connectionPool.connections) {
conn = this.connectionPool.connections[0];
}
const connUrl = conn && conn.url;
span._setDestinationContext(
getDBDestination(
connUrl && connUrl.hostname,
connUrl && connUrl.port,
),
);
// Gather some HTTP context.
// We are *not* including the response headers b/c they are boring:
//
// X-elastic-product: Elasticsearch
// content-type: application/json
// content-length: ...
//
// Getting the ES client request "DiagnosticResult" object has some edge cases:
// - In v7 using a callback, we always get it as `result`.
// - In v7 using a Promise, if the promise is rejected, then `result` is
// not passed.
// - In v8, `result` only includes HTTP response info if `options.meta`
// is true. We use the diagnostic 'response' event instead.
// - In v7, see the limitation note above for the rare start case where
// the diagnostic 'response' event may have the wrong currentSpan.
// The result is that with Promise usage of v7, ES client requests that
// are queued behind the "product-check" and that reject, won't have a
// `diagResult`.
const httpContext = {};
let haveHttpContext = false;
let diagResult = isGteV8 ? null : result;
if (!diagResult) {
diagResult = diagResultFromSpan.get(span);
if (diagResult) {
diagResultFromSpan.delete(span);
}
}
if (diagResult) {
if (diagResult.statusCode) {
haveHttpContext = true;
httpContext.status_code = diagResult.statusCode;
}
if (diagResult.headers && 'content-length' in diagResult.headers) {
const contentLength = Number(
diagResult.headers['content-length'],
);
if (!isNaN(contentLength)) {
haveHttpContext = true;
httpContext.response = { encoded_body_size: contentLength };
}
}
}
// Reconstruct the full URL (including query params).
let origin;
if (connUrl) {
origin = connUrl.origin;
} else if (
diagResult &&
diagResult.meta &&
diagResult.meta.connection &&
diagResult.meta.connection.url
) {
try {
origin = new URL(diagResult.meta.connection.url).origin;
} catch (_ignoredErr) {}
}
if (origin && params && params.path) {
const fullUrl = new URL(origin);
fullUrl.pathname = params.path;
fullUrl.search = new URLSearchParams(params.querystring).toString();
httpContext.url = fullUrl.toString();
haveHttpContext = true;
}
if (haveHttpContext) {
span.setHttpContext(httpContext);
}
// Set DB context.
const dbContext = {
type: 'elasticsearch',
};
if (params) {
const statement = getElasticsearchDbStatement(
params.path,
params.body || params.bulkBody,
elasticsearchCaptureBodyUrlsRegExp,
);
if (statement) {
dbContext.statement = statement;
}
}
const clusterName = getESClusterName(diagResult);
if (clusterName) {
dbContext.instance = clusterName;
}
span.setDbContext(dbContext);
if (err) {
// Error properties are specified here:
// https://github.com/elastic/elasticsearch-js/blob/master/lib/errors.d.ts
// - We capture some data from ResponseError, which is for
// Elasticsearch API errors:
// https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#common-options-error-options
// - Otherwise we explicitly turn off `captureAttributes` to avoid
// grabbing potentially large and sensitive properties like
// `err.data` on DeserializationError.
const errOpts = {
captureAttributes: false,
};
const errBody = err.body;
if (err.name === 'ResponseError' && errBody && errBody.error) {
// Include some data from the Elasticsearch API response body:
// https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#common-options-error-options
const errType = errBody.error.type;
if (errType) {
// Specialize `error.exception.type` for better error grouping.
errOpts.exceptionType = `ResponseError (${errType})`;
}
errOpts.custom = {
type: errType,
reason: errBody.error.reason,
status: errBody.status,
};
if (errBody.error.caused_by) {
errOpts.custom.caused_by = errBody.error.caused_by;
}
}
agent.captureError(err, errOpts);
}
span.end();
},
);
if (typeof cb === 'function') {
const wrappedCb = (err, result) => {
finish(err, result);
ins.withRunContext(parentRunContext, cb, this, err, result);
};
return ins.withRunContext(
spanRunContext,
origRequest,
this,
params,
options,
wrappedCb,
);
} else {
const origPromise = ins.withRunContext(
spanRunContext,
origRequest,
this,
...arguments,
);
origPromise.then(
function onResolve(result) {
finish(null, result);
},
function onReject(err) {
finish(err, null);
},
);
return origPromise;
}
};
}