@instana/core
Advanced tools
Comparing version 1.106.5 to 1.106.6
{ | ||
"name": "@instana/core", | ||
"version": "1.106.5", | ||
"version": "1.106.6", | ||
"description": "Core library for Instana's Node.js packages", | ||
@@ -136,3 +136,3 @@ "main": "src/index.js", | ||
}, | ||
"gitHead": "cc05d8aa93e154b98d7befa9557789064c66aa58" | ||
"gitHead": "a13253b1e65d4c66d32495e092d8990073a0240f" | ||
} |
'use strict'; | ||
const url = require('url'); | ||
const shimmer = require('shimmer'); | ||
const requireHook = require('../../../util/requireHook'); | ||
@@ -8,2 +11,10 @@ const tracingUtil = require('../../tracingUtil'); | ||
let logger; | ||
logger = require('../../../logger').getLogger('tracing/elasticsearch', newLogger => { | ||
logger = newLogger; | ||
}); | ||
const methodToActionRegex = /^(\w+?)(?:Api)?\.(?:.+) \[as (\w+)\]$/; | ||
const endpointToIdRegex = /^\/[^/]+\/_doc\/([^/]+)$/; | ||
let isActive = false; | ||
@@ -16,2 +27,11 @@ | ||
function instrument(es) { | ||
const ESAPI = require('@elastic/elasticsearch/api'); | ||
if (isConstructor(ESAPI)) { | ||
instrumentTransport(es); | ||
} else { | ||
instrumentApiLayer(es, ESAPI); | ||
} | ||
} | ||
function instrumentApiLayer(es, ESAPI) { | ||
const OriginalClient = es.Client; | ||
@@ -23,3 +43,3 @@ if (!OriginalClient || typeof OriginalClient !== 'function') { | ||
const actionPaths = []; | ||
forEachApiAction(actionPath => { | ||
forEachApiAction(ESAPI, actionPath => { | ||
actionPaths.push(actionPath); | ||
@@ -106,17 +126,3 @@ }); | ||
if (action === 'mget' && params.body && params.body.docs && Array.isArray(params.body.docs)) { | ||
getSpanDataFromMget1(span, params.body.docs); | ||
} else if (action === 'mget' && params.body && params.body.ids && Array.isArray(params.body.ids)) { | ||
getSpanDataFromMget2(span, params); | ||
} else if (action === 'msearch' && Array.isArray(params.body)) { | ||
getSpanDataFromMsearch(span, params.body); | ||
} else { | ||
span.data.elasticsearch.index = toStringEsMultiParameter(params.index); | ||
span.data.elasticsearch.type = toStringEsMultiParameter(params.type); | ||
span.data.elasticsearch.stats = toStringEsMultiParameter(params.stats); | ||
span.data.elasticsearch.id = params.id; | ||
if (action.indexOf('search') === 0) { | ||
span.data.elasticsearch.query = tracingUtil.shortenDatabaseStatement(JSON.stringify(params)); | ||
} | ||
} | ||
processParams(span, params); | ||
@@ -168,2 +174,4 @@ if (callbackIndex >= 0) { | ||
} | ||
parseConnectionFromResult(span, result); | ||
span.d = Date.now() - span.ts; | ||
@@ -180,5 +188,38 @@ span.transmit(); | ||
} | ||
if (error.meta && error.meta.meta) { | ||
parseConnectionFromResult(span, error.meta); | ||
} | ||
span.transmit(); | ||
} | ||
function parseConnectionFromResult(span, result) { | ||
// Result can also be a part of the error object, both have the meta.connection attribute. | ||
// For the error object it is in error.meta.meta.connection (see onError). | ||
if (!span.data.elasticsearch.cluster && !span.data.elasticsearch.address && result.meta && result.meta.connection) { | ||
const connectionUrl = result.meta.connection.url; | ||
if (connectionUrl && connectionUrl instanceof url.URL) { | ||
span.data.elasticsearch.address = connectionUrl.hostname; | ||
span.data.elasticsearch.port = connectionUrl.port; | ||
} | ||
} | ||
} | ||
function processParams(span, params) { | ||
const action = span.data.elasticsearch.action; | ||
const body = (params && params.body) || (params && params.bulkBody); | ||
if (action === 'mget' && body && body.docs && Array.isArray(body.docs)) { | ||
getSpanDataFromMget1(span, body.docs); | ||
} else if (action === 'mget' && body && body.ids && Array.isArray(body.ids)) { | ||
getSpanDataFromMget2(span, params); | ||
} else if (action === 'msearch' && Array.isArray(body)) { | ||
getSpanDataFromMsearch(span, body); | ||
} else { | ||
span.data.elasticsearch.index = toStringEsMultiParameter(params.index); | ||
span.data.elasticsearch.type = toStringEsMultiParameter(params.type); | ||
span.data.elasticsearch.id = params.id; | ||
if (action.indexOf('search') === 0) { | ||
span.data.elasticsearch.query = tracingUtil.shortenDatabaseStatement(JSON.stringify(params)); | ||
} | ||
} | ||
} | ||
function toStringEsMultiParameter(param) { | ||
@@ -204,3 +245,2 @@ if (param == null) { | ||
const types = []; | ||
const stats = []; | ||
const ids = []; | ||
@@ -210,3 +250,2 @@ for (let i = 0; i < docs.length; i++) { | ||
collectParamFrom(docs[i], '_type', types); | ||
collectParamFrom(docs[i], '_stats', stats); | ||
collectParamFrom(docs[i], '_id', ids); | ||
@@ -216,3 +255,2 @@ } | ||
span.data.elasticsearch.type = types.length > 0 ? types.join(',') : undefined; | ||
span.data.elasticsearch.stats = stats.length > 0 ? stats.join(',') : undefined; | ||
span.data.elasticsearch.id = ids.length > 0 ? ids.join(',') : undefined; | ||
@@ -222,6 +260,9 @@ } | ||
function getSpanDataFromMget2(span, params) { | ||
if (!params) { | ||
return; | ||
} | ||
const body = (params && params.body) || (params && params.bulkBody); | ||
span.data.elasticsearch.index = params.index ? toStringEsMultiParameter(params.index) : undefined; | ||
span.data.elasticsearch.type = params.index ? toStringEsMultiParameter(params.type) : undefined; | ||
span.data.elasticsearch.stats = params.index ? toStringEsMultiParameter(params.stats) : undefined; | ||
span.data.elasticsearch.id = params.body.ids.length > 0 ? params.body.ids.join(',') : undefined; | ||
span.data.elasticsearch.id = body && body.ids.length > 0 ? body.ids.join(',') : undefined; | ||
} | ||
@@ -232,3 +273,2 @@ | ||
const types = []; | ||
const stats = []; | ||
const query = []; | ||
@@ -238,3 +278,2 @@ for (let i = 0; i < body.length; i++) { | ||
collectParamFrom(body[i], 'type', types); | ||
collectParamFrom(body[i], 'stats', stats); | ||
collectParamFrom(body[i], 'query', query); | ||
@@ -244,3 +283,2 @@ } | ||
span.data.elasticsearch.type = types.length > 0 ? types.join(',') : undefined; | ||
span.data.elasticsearch.stats = stats.length > 0 ? stats.join(',') : undefined; | ||
span.data.elasticsearch.query = query.length > 0 ? tracingUtil.shortenDatabaseStatement(query.join(',')) : undefined; | ||
@@ -258,4 +296,141 @@ } | ||
function forEachApiAction(fn) { | ||
const esApi = require('@elastic/elasticsearch/api')({ | ||
function instrumentTransport(es) { | ||
// Starting with 7.9.1, the export of @elastic/elasticsearch/api expects to be called as a constructor. In fact, | ||
// @elastic/elasticsearch#Client now inherits from @elastic/elasticsearch/api. See | ||
// https://github.com/elastic/elasticsearch-js/commit/a064f0f357ea5797cb8a784671b85a6b0c88626d and | ||
// https://github.com/elastic/elasticsearch-js/pull/1314 for details. Also, starting with that version, some API | ||
// methods are added via Object.defineProperties with the default settings and only a getter, making it impossible | ||
// to override/wrap them. For those versions we fall back to instrumenting the transport layer instead of the API. | ||
if (es.Transport && es.Transport.prototype) { | ||
shimmer.wrap(es.Transport.prototype, 'request', shimRequest); | ||
} else { | ||
logger.error( | ||
'Cannot instrument @elastic/elasticsearch. Either es.Transport or es.Transport.prototype does not exist.' | ||
); | ||
} | ||
} | ||
function shimRequest(esReq) { | ||
return function() { | ||
if (!isActive || !cls.isTracing()) { | ||
return esReq.apply(this, arguments); | ||
} | ||
const originalArgs = new Array(arguments.length); | ||
for (let i = 0; i < arguments.length; i++) { | ||
originalArgs[i] = arguments[i]; | ||
} | ||
return instrumentedRequest(this, esReq, originalArgs); | ||
}; | ||
} | ||
function instrumentedRequest(ctx, origEsReq, originalArgs) { | ||
const parentSpan = cls.getCurrentSpan(); | ||
if (constants.isExitSpan(parentSpan)) { | ||
return origEsReq.apply(ctx, originalArgs); | ||
} | ||
// normalize arguments | ||
let params = originalArgs[0] || {}; | ||
let options = originalArgs[1]; | ||
let callbackIndex = 2; | ||
let originalCallback = originalArgs[callbackIndex]; | ||
if (typeof originalCallback !== 'function') { | ||
if (typeof options === 'function') { | ||
callbackIndex = 1; | ||
originalCallback = originalArgs[callbackIndex]; | ||
} else if (typeof params === 'function') { | ||
callbackIndex = 0; | ||
originalCallback = originalArgs[callbackIndex]; | ||
params = {}; | ||
} else { | ||
callbackIndex = -1; | ||
originalCallback = null; | ||
} | ||
} | ||
const httpPath = params.path; | ||
return cls.ns.runAndReturn(() => { | ||
const span = cls.startSpan('elasticsearch', constants.EXIT); | ||
span.stack = tracingUtil.getStackTrace(instrumentedRequest, 1); | ||
span.data.elasticsearch = { | ||
endpoint: httpPath | ||
}; | ||
findActionInStack(span); | ||
processParams(span, params); | ||
parseIdFromPath(span, httpPath); | ||
const q = params.querystring; | ||
if (q) { | ||
if (typeof q === 'string') { | ||
span.data.elasticsearch.query = tracingUtil.shortenDatabaseStatement(q); | ||
} else if (typeof q === 'object' && Object.keys(q).length > 0) { | ||
span.data.elasticsearch.query = tracingUtil.shortenDatabaseStatement(JSON.stringify(q)); | ||
} | ||
} | ||
if (callbackIndex >= 0) { | ||
originalArgs[callbackIndex] = cls.ns.bind(function(error, result) { | ||
if (error) { | ||
onError(span, error); | ||
} else { | ||
onSuccess(span, result); | ||
} | ||
return originalCallback.apply(this, arguments); | ||
}); | ||
return origEsReq.apply(ctx, originalArgs); | ||
} else { | ||
try { | ||
return origEsReq.apply(ctx, originalArgs).then(onSuccess.bind(null, span), error => { | ||
onError(span, error); | ||
throw error; | ||
}); | ||
} catch (e) { | ||
// Immediately cleanup on synchronous errors. | ||
throw e; | ||
} | ||
} | ||
}); | ||
} | ||
function findActionInStack(span) { | ||
if (!span.stack) { | ||
return; | ||
} | ||
const esApiFrames = span.stack.filter(frame => frame.c && frame.c.includes('@elastic/elasticsearch/api')); | ||
if (esApiFrames.length === 0) { | ||
return; | ||
} | ||
let esApiMethod = esApiFrames[esApiFrames.length - 1].m; | ||
if (!esApiMethod) { | ||
return; | ||
} | ||
const matchResult = methodToActionRegex.exec(esApiMethod); | ||
if (matchResult) { | ||
if (matchResult[1] === 'Client') { | ||
// Top level API methods directly on Client, like `Client.indexApi [as index]`: | ||
span.data.elasticsearch.action = `${matchResult[2].toLowerCase()}`; | ||
} else { | ||
// Nested API, like `IndicesApi.indicesRefreshApi [as refresh]`: | ||
span.data.elasticsearch.action = `${matchResult[1].toLowerCase()}.${matchResult[2].toLowerCase()}`; | ||
} | ||
} else { | ||
// fall back: use the full method name | ||
span.data.elasticsearch.action = esApiMethod; | ||
} | ||
} | ||
function parseIdFromPath(span, httpPath) { | ||
if (httpPath) { | ||
const matchResult = endpointToIdRegex.exec(httpPath); | ||
if (matchResult) { | ||
span.data.elasticsearch.id = matchResult[1]; | ||
} | ||
} | ||
} | ||
function forEachApiAction(ESAPI, fn) { | ||
const esApiFromBuildApi = ESAPI({ | ||
makeRequest: function dummyMakeRequest() {}, | ||
@@ -265,3 +440,3 @@ ConfigurationError: function DummyConfigurationError() {}, | ||
}); | ||
forEachKeyRecursive(esApi, [], fn); | ||
forEachKeyRecursive(esApiFromBuildApi, [], fn); | ||
} | ||
@@ -298,2 +473,6 @@ | ||
function isConstructor(ESAPI) { | ||
return ESAPI && typeof ESAPI.toString === 'function' && ESAPI.toString().includes('this['); | ||
} | ||
exports.activate = function activate() { | ||
@@ -300,0 +479,0 @@ isActive = true; |
@@ -58,3 +58,13 @@ 'use strict'; | ||
if (typeof transformerFn === 'function') { | ||
cacheEntry.moduleExports = transformerFn(cacheEntry.moduleExports) || cacheEntry.moduleExports; | ||
try { | ||
cacheEntry.moduleExports = transformerFn(cacheEntry.moduleExports) || cacheEntry.moduleExports; | ||
} catch (e) { | ||
logger.error(`Cannot instrument ${moduleName} due to an error in instrumentation: ${e}`); | ||
if (e.message) { | ||
logger.error(e.message); | ||
} | ||
if (e.stack) { | ||
logger.error(e.stack); | ||
} | ||
} | ||
} else { | ||
@@ -61,0 +71,0 @@ logger.error( |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
384512
10430