elastic-apm-node
Advanced tools
Comparing version 3.27.0 to 3.28.0
@@ -350,2 +350,3 @@ /// <reference types="node" /> | ||
childOf?: Transaction | Span | string; | ||
exitSpan?: boolean; | ||
} | ||
@@ -352,0 +353,0 @@ |
@@ -174,3 +174,3 @@ 'use strict' | ||
Agent.prototype.startSpan = function (name, type, subtype, action, { childOf } = {}) { | ||
Agent.prototype.startSpan = function (name, type, subtype, action, { startTime, childOf, exitSpan } = {}) { | ||
return this._instrumentation.startSpan.apply(this._instrumentation, arguments) | ||
@@ -177,0 +177,0 @@ } |
@@ -121,3 +121,3 @@ 'use strict' | ||
const error = new CallbackCoordinationError( | ||
'all callbacks failed', | ||
'no response from any callback, no cloud metadata will be set (normal outside of cloud env.)', | ||
this.errors | ||
@@ -124,0 +124,0 @@ ) |
@@ -318,3 +318,4 @@ 'use strict' | ||
} else { | ||
// Zero-conf support: use package.json#name, else // "nodejs_service". | ||
// Zero-conf support: use package.json#name, else | ||
// `unknown-${service.agent.name}-service`. | ||
try { | ||
@@ -326,3 +327,3 @@ this.serviceName = serviceNameFromPackageJson() | ||
if (!this.serviceName) { | ||
this.serviceName = 'nodejs_service' | ||
this.serviceName = 'unknown-nodejs-service' | ||
} | ||
@@ -530,3 +531,4 @@ } | ||
// Disallow some weird sanitized values. For example, it is better to | ||
// have the fallback "nodejs_service" than "_" or "____" or " ". | ||
// have the fallback "unknown-{service.agent.name}-service" than "_" or | ||
// "____" or " ". | ||
const ALL_NON_ALPHANUMERIC = /^[ _-]*$/ | ||
@@ -578,3 +580,3 @@ if (ALL_NON_ALPHANUMERIC.test(serviceName)) { | ||
// | ||
// https://github.com/elastic/apm/blob/master/specs/agents/tracing-sampling.md | ||
// https://github.com/elastic/apm/blob/main/specs/agents/tracing-sampling.md | ||
function normalizeTransactionSampleRate (opts, logger) { | ||
@@ -848,3 +850,3 @@ if ('transactionSampleRate' in opts) { | ||
// | ||
// Per https://github.com/elastic/apm/blob/master/specs/agents/transport.md#user-agent | ||
// Per https://github.com/elastic/apm/blob/main/specs/agents/transport.md#user-agent | ||
// the pattern is roughly this: | ||
@@ -931,5 +933,6 @@ // $repoName/$version ($serviceName $serviceVersion) | ||
// Debugging | ||
// Debugging/testing options | ||
logger: clientLogger, | ||
payloadLogFile: conf.payloadLogFile, | ||
apmServerVersion: conf.apmServerVersion, | ||
@@ -936,0 +939,0 @@ // Container conf |
@@ -149,2 +149,3 @@ 'use strict' | ||
error.transaction = { | ||
name: args.trans.name, | ||
type: args.trans.type, | ||
@@ -151,0 +152,0 @@ sampled: args.trans.sampled |
@@ -31,11 +31,4 @@ 'use strict' | ||
// `path`, `query`, and `body` can all be null or undefined. | ||
exports.setElasticsearchDbContext = function (span, path, query, body, isLegacy) { | ||
exports.setElasticsearchDbContext = function (span, path, query, body) { | ||
if (path && pathIsAQuery.test(path)) { | ||
// From @elastic/elasticsearch: A read of Transport.js suggests query and | ||
// body will always be serialized strings, however the documented | ||
// `TransportRequestParams` (`ConnectionRequestParams` in version 8) | ||
// allows for non-strings, so we will be defensive. | ||
// | ||
// From legacy elasticsearch: query will be an object and body will be an | ||
// object, or an array of objects, e.g. for bulk endpoints. | ||
const parts = [] | ||
@@ -45,3 +38,3 @@ if (query) { | ||
parts.push(query) | ||
} else if (isLegacy && typeof (query) === 'object') { | ||
} else if (typeof (query) === 'object') { | ||
const encodedQuery = querystring.encode(query) | ||
@@ -56,8 +49,14 @@ if (encodedQuery) { | ||
parts.push(body) | ||
} else if (isLegacy) { | ||
if (Array.isArray(body)) { | ||
parts.push(body.map(JSON.stringify).join('\n')) // ndjson | ||
} else if (typeof (body) === 'object') { | ||
} else if (Buffer.isBuffer(body) || typeof body.pipe === 'function') { | ||
// Never serialize a Buffer or a Readable. These guards mirror | ||
// `shouldSerialize()` in the ES client, e.g.: | ||
// https://github.com/elastic/elastic-transport-js/blob/069172506d1fcd544b23747d8c2d497bab053038/src/Transport.ts#L614-L618 | ||
} else if (Array.isArray(body)) { | ||
try { | ||
parts.push(body.map(JSON.stringify).join('\n') + '\n') // ndjson | ||
} catch (_ignoredErr) {} | ||
} else if (typeof (body) === 'object') { | ||
try { | ||
parts.push(JSON.stringify(body)) | ||
} | ||
} catch (_ignoredErr) {} | ||
} | ||
@@ -64,0 +63,0 @@ } |
@@ -149,8 +149,8 @@ 'use strict' | ||
// Attempt to use the span context as a traceparent header. | ||
// If the transaction is unsampled the span will not exist, | ||
// however a traceparent header must still be propagated | ||
// to indicate requested services should not be sampled. | ||
// Use the transaction context as the parent, in this case. | ||
var parent = span || ins.currTransaction() | ||
// 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. | ||
var parent = span || parentRunContext.currSpan() || parentRunContext.currTransaction() | ||
if (parent && parent._context) { | ||
@@ -157,0 +157,0 @@ const headerValue = parent._context.toTraceParentString() |
@@ -86,3 +86,3 @@ 'use strict' | ||
} | ||
return this._runCtxMgr.active().currTransaction() || null | ||
return this._runCtxMgr.active().currTransaction() | ||
} | ||
@@ -285,2 +285,7 @@ | ||
// https://github.com/elastic/apm/blob/main/specs/agents/tracing-sampling.md#non-sampled-transactions | ||
if (!transaction.sampled && !agent._transport.supportsKeepingUnsampledTransaction()) { | ||
return | ||
} | ||
var payload = agent._transactionFilters.process(transaction._encode()) | ||
@@ -287,0 +292,0 @@ if (!payload) { |
@@ -8,2 +8,23 @@ 'use strict' | ||
// to hook into all ES server interactions. | ||
// | ||
// Limitations: | ||
// - In @elastic/elasticsearch >=7.14 <8, the diagnostic events sent for ES | ||
// spans started before the product-check is finished will have an incorrect | ||
// `currentSpan`. | ||
// | ||
// An Elasticsearch (ES) request typically results in a single HTTP request to | ||
// the server. For some of the later 7.x versions of @elastic/elasticsearch | ||
// there is a product-check "GET /" that blocks the *first* request to the | ||
// server. The handling of ES requests are effectively queued until that | ||
// product-check is complete. When they *do* run, the async context is that | ||
// of the initial ES span. This means that `apm.currentSpan` inside an ES | ||
// client diagnostic event for these queued ES requests will be wrong. | ||
// Currently the APM agent is not patching for this. | ||
// | ||
// - When using the non-default `asyncHooks=false` APM Agent option with | ||
// @elastic/elasticsearch >=8 instrumentation, the diagnostic events do not | ||
// have the async run context of the current span. There are two impacts: | ||
// 1. Elasticsearch tracing spans will not have additional "http" context | ||
// about the underlying HTTP request. | ||
// 2. Users cannot access `apm.currentSpan` inside a diagnostic event handler. | ||
@@ -14,3 +35,3 @@ const semver = require('semver') | ||
const { setElasticsearchDbContext } = require('../../elasticsearch-shared') | ||
const constants = require('../../../constants') | ||
const shimmer = require('../../shimmer') | ||
@@ -26,79 +47,151 @@ module.exports = function (elasticsearch, agent, { version, enabled }) { | ||
// Before v7.7.0 the Transport#request() implementation's Promises support | ||
// would re-call `this.request(...)` inside a Promise. | ||
const doubleCallsRequestIfNoCb = semver.lt(version, '7.7.0') | ||
const ins = agent._instrumentation | ||
const isGteV8 = semver.satisfies(version, '>=8', { includePrerelease: true }) | ||
class ApmClient extends elasticsearch.Client { | ||
constructor (...args) { | ||
super(...args) | ||
agent.logger.debug('shimming elasticsearch.Transport.prototype.{request,getConnection}') | ||
shimmer.wrap(elasticsearch.Transport && elasticsearch.Transport.prototype, 'request', wrapRequest) | ||
shimmer.wrap(elasticsearch.Transport && elasticsearch.Transport.prototype, 'getConnection', wrapGetConnection) | ||
shimmer.wrap(elasticsearch, 'Client', wrapClient) | ||
// The thing with the `.on()` method for getting observability events. | ||
const diagnostic = isGteV8 ? this.diagnostic : this | ||
// Tracking the ES client Connection object and DiagnosticResult for each | ||
// active span. Use WeakMap to avoid a leak from possible spans that don't | ||
// end. | ||
const connFromSpan = new WeakMap() | ||
const diagResultFromSpan = new WeakMap() | ||
// Mapping an ES client event `result` to its active span. | ||
// - Use WeakMap to avoid a leak from possible spans that don't end. | ||
// - WeakMap allows us to key off the ES client `request` object itself, | ||
// which means we don't need to rely on `request.id`, which might be | ||
// unreliable because it is user-settable (see `generateRequestId` at | ||
// https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/observability.html) | ||
const spanFromEsResult = new WeakMap() | ||
return elasticsearch | ||
diagnostic.on('request', (err, result) => { | ||
let request = null | ||
let connection = null | ||
if (result && result.meta) { | ||
request = result.meta.request | ||
connection = result.meta.connection | ||
} | ||
let paramMethod = null | ||
let paramPath = null | ||
let paramQueryString = null | ||
let paramBody = null | ||
if (request && request.params) { | ||
// request.params can be null with ProductNotSupportedError and | ||
// DeserializationError. | ||
paramMethod = request.params.method | ||
paramPath = request.params.path | ||
paramQueryString = request.params.querystring | ||
paramBody = request.params.body | ||
} | ||
agent.logger.debug('intercepted call to @elastic/elasticsearch "request" event %o', | ||
{ id: request && request.id, method: paramMethod, path: paramPath }) | ||
function wrapClient (OrigClient) { | ||
class ClientTraced extends OrigClient { | ||
constructor (...args) { | ||
super(...args) | ||
const diagnostic = isGteV8 ? this.diagnostic : this | ||
diagnostic.on('response', (_err, result) => { | ||
if (result) { | ||
const currSpan = ins.currSpan() | ||
if (currSpan) { | ||
diagResultFromSpan[currSpan] = result | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
return ClientTraced | ||
} | ||
let span = spanFromEsResult.get(result) | ||
// Transport#request() calls Transport#getConnection() when it is ready to | ||
// make the HTTP request. This returns the actual connection to be used for | ||
// the request. This is limited, however: | ||
// - `getConnection()` is not called if the request was aborted early. | ||
// - If all connections are marked dead, then this returns null. | ||
// - We are assuming this is called with the correct async context. See | ||
// "Limitations" above. | ||
function wrapGetConnection (origGetConnection) { | ||
return function wrappedGetConnection (opts) { | ||
const conn = origGetConnection.apply(this, arguments) | ||
const currSpan = ins.currSpan() | ||
if (conn && currSpan) { | ||
connFromSpan[currSpan] = conn | ||
} | ||
return conn | ||
} | ||
} | ||
if (err) { | ||
agent.captureError(err) | ||
if (span !== undefined) { | ||
span.end() | ||
spanFromEsResult.delete(result) | ||
} | ||
return | ||
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 DB context. | ||
// In @elastic/elasticsearch@7, `Transport#request` encodes | ||
// `params.{querystring,body}` in-place; use it. In >=8 this encoding is | ||
// no longer in-place. A better eventual solution would be to wrap | ||
// `Connection.request` to capture the serialized params. | ||
setElasticsearchDbContext( | ||
span, | ||
params && params.path, | ||
params && params.querystring, | ||
params && (params.body || params.bulkBody)) | ||
// 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[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(span, | ||
connUrl && connUrl.hostname, connUrl && connUrl.port)) | ||
// With retries (see `makeRequest` in Transport.js) each attempt will | ||
// emit this "request" event using the same `result` object. The | ||
// intent is to have one Elasticsearch span plus an HTTP span for each | ||
// attempt. | ||
if (!span) { | ||
const spanName = `Elasticsearch: ${paramMethod || '<UnknownMethod>'} ${paramPath || '<UnknownPath>'}` | ||
span = agent.startSpan(spanName, 'db', 'elasticsearch', 'request') | ||
if (span) { | ||
spanFromEsResult.set(result, span) | ||
// Gather some HTTP context from the "DiagnosticResult" object. | ||
// 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 `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`. | ||
let diagResult = isGteV8 ? null : result | ||
if (!diagResult) { | ||
diagResult = diagResultFromSpan[span] | ||
if (diagResult) { | ||
diagResultFromSpan.delete(span) | ||
} | ||
} | ||
if (!span) { | ||
return | ||
if (diagResult) { | ||
const httpContext = {} | ||
let haveHttpContext = false | ||
if (diagResult.statusCode) { | ||
haveHttpContext = true | ||
httpContext.status_code = diagResult.statusCode | ||
} | ||
// *Not* currently adding headers because | ||
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 } | ||
} | ||
} | ||
if (haveHttpContext) { | ||
span.setHttpContext(httpContext) | ||
} | ||
} | ||
setElasticsearchDbContext(span, paramPath, paramQueryString, paramBody, false) | ||
if (connection && connection.url) { | ||
const { hostname, port } = connection.url | ||
span.setDestinationContext( | ||
getDBDestination(span, hostname, port)) | ||
} | ||
}) | ||
diagnostic.on('response', (err, result) => { | ||
const span = spanFromEsResult.get(result) | ||
if (err) { | ||
@@ -114,4 +207,3 @@ // Error properties are specified here: | ||
const errOpts = { | ||
captureAttributes: false, | ||
skipOutcome: true | ||
captureAttributes: false | ||
} | ||
@@ -130,25 +222,29 @@ if (err.name === 'ResponseError' && err.body && err.body.error) { | ||
} | ||
// The capture error method normally sets an outcome on the | ||
// active span. However, the Elasticsearch client span (the span | ||
// we're concerned with here) is no longer the active span. | ||
// Therefore, we manully set an outcome here, and also set | ||
// errOpts.skipOutcome above. The errOpts.skipOutcome options | ||
// instructs captureError to _not_ set the outcome on the active span | ||
if (span !== undefined) { | ||
span._setOutcomeFromErrorCapture(constants.OUTCOME_FAILURE) | ||
} | ||
agent.captureError(err, errOpts) | ||
} | ||
if (span !== undefined) { | ||
span.end() | ||
spanFromEsResult.delete(result) | ||
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 | ||
} | ||
} | ||
} | ||
agent.logger.debug('subclassing @elastic/elasticsearch.Client') | ||
return Object.assign(elasticsearch, { Client: ApmClient }) | ||
} |
@@ -57,3 +57,3 @@ 'use strict' | ||
const span = ins.createSpan(name, TYPE, SUBTYPE, opName) | ||
const span = ins.createSpan(name, TYPE, SUBTYPE, opName, { exitSpan: true }) | ||
if (!span) { | ||
@@ -73,3 +73,4 @@ return orig.apply(request, origArguments) | ||
// 'complete'. | ||
const region = request.httpRequest && request.httpRequest.region | ||
const httpRequest = request.httpRequest | ||
const region = httpRequest && httpRequest.region | ||
@@ -79,3 +80,3 @@ // Destination context. | ||
// the bucket is in a different region. | ||
const endpoint = request.httpRequest && request.httpRequest.endpoint | ||
const endpoint = httpRequest && httpRequest.endpoint | ||
const destContext = { | ||
@@ -100,4 +101,26 @@ service: { | ||
if (response) { | ||
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/HttpResponse.html | ||
const httpResponse = response.httpResponse | ||
let statusCode | ||
if (httpResponse) { | ||
statusCode = httpResponse.statusCode | ||
// Set HTTP context. Some context not being set, though it is available: | ||
// - method: Not that helpful. | ||
// - url: Mostly redundant with context.destination.address. | ||
// - response.headers: A lot of added size for uncertain utility. The | ||
// inclusion of Amazon's request ID headers might be worth it. | ||
const httpContext = { | ||
status_code: statusCode | ||
} | ||
const encodedBodySize = Buffer.isBuffer(httpResponse.body) && httpResponse.body.byteLength | ||
if (encodedBodySize) { | ||
// I'm not actually sure if this might be decoded_body_size. | ||
httpContext.response = { encoded_body_size: encodedBodySize } | ||
} | ||
span.setHttpContext(httpContext) | ||
} | ||
// Follow the spec for HTTP client span outcome. | ||
// https://github.com/elastic/apm/blob/master/specs/agents/tracing-instrumentation-http.md#outcome | ||
// https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-http.md#outcome | ||
// | ||
@@ -107,3 +130,2 @@ // For example, a S3 GetObject conditional request (e.g. using the | ||
// statusCode=304. This is a *successful* outcome. | ||
const statusCode = response.httpResponse && response.httpResponse.statusCode | ||
if (statusCode) { | ||
@@ -110,0 +132,0 @@ span._setOutcomeFromHttpStatusCode(statusCode) |
@@ -15,2 +15,4 @@ 'use strict' | ||
const ins = agent._instrumentation | ||
if (cassandra.Client) { | ||
@@ -35,3 +37,3 @@ if (semver.gte(version, '4.4.0')) { | ||
return async function wrappedAsyncConnect () { | ||
const span = agent.startSpan('Cassandra: Connect', 'db', 'cassandra', 'connect') | ||
const span = ins.createSpan('Cassandra: Connect', 'db', 'cassandra', 'connect') | ||
try { | ||
@@ -47,3 +49,3 @@ return await original.apply(this, arguments) | ||
return function wrappedConnect (callback) { | ||
const span = agent.startSpan('Cassandra: Connect', 'db', 'cassandra', 'connect') | ||
const span = ins.createSpan('Cassandra: Connect', 'db', 'cassandra', 'connect') | ||
if (!span) { | ||
@@ -86,3 +88,3 @@ return original.apply(this, arguments) | ||
return function wrappedBatch (queries, options, callback) { | ||
const span = agent.startSpan('Cassandra: Batch query', 'db', 'cassandra', 'query') | ||
const span = ins.createSpan('Cassandra: Batch query', 'db', 'cassandra', 'query') | ||
if (!span) { | ||
@@ -131,3 +133,3 @@ return original.apply(this, arguments) | ||
return function wrappedExecute (query, params, options, callback) { | ||
const span = agent.startSpan(null, 'db', 'cassandra', 'query') | ||
const span = ins.createSpan(null, 'db', 'cassandra', 'query') | ||
if (!span) { | ||
@@ -171,3 +173,3 @@ return original.apply(this, arguments) | ||
return function wrappedEachRow (query, params, options, rowCallback, callback) { | ||
const span = agent.startSpan(null, 'db', 'cassandra', 'query') | ||
const span = ins.createSpan(null, 'db', 'cassandra', 'query') | ||
if (!span) { | ||
@@ -174,0 +176,0 @@ return original.apply(this, arguments) |
@@ -59,2 +59,4 @@ 'use strict' | ||
const ins = agent._instrumentation | ||
agent.logger.debug('shimming elasticsearch.Transport.prototype.request') | ||
@@ -67,3 +69,3 @@ shimmer.wrap(elasticsearch.Transport && elasticsearch.Transport.prototype, 'request', wrapRequest) | ||
return function wrappedRequest (params, cb) { | ||
var span = agent.startSpan(null, 'db', 'elasticsearch', 'request') | ||
var span = ins.createSpan(null, 'db', 'elasticsearch', 'request', { exitSpan: true }) | ||
var id = span && span.transaction.id | ||
@@ -79,3 +81,3 @@ var method = params && params.method | ||
setElasticsearchDbContext(span, path, params && params.query, | ||
params && params.body, true) | ||
params && params.body) | ||
@@ -91,2 +93,4 @@ // Get the remote host information from elasticsearch Transport options. | ||
const parentRunContext = ins.currRunContext() | ||
const spanRunContext = parentRunContext.enterSpan(span) | ||
if (typeof cb === 'function') { | ||
@@ -96,7 +100,7 @@ var args = Array.prototype.slice.call(arguments) | ||
span.end() | ||
return cb.apply(this, arguments) | ||
ins.withRunContext(parentRunContext, cb, this, ...arguments) | ||
} | ||
return original.apply(this, args) | ||
return ins.withRunContext(spanRunContext, original, this, ...args) | ||
} else { | ||
const originalPromise = original.apply(this, arguments) | ||
const originalPromise = ins.withRunContext(spanRunContext, original, this, ...arguments) | ||
@@ -103,0 +107,0 @@ const descriptors = Object.getOwnPropertyDescriptors(originalPromise) |
@@ -19,2 +19,4 @@ 'use strict' | ||
const ins = agent._instrumentation | ||
if (mongodb.Server) { | ||
@@ -53,5 +55,5 @@ agent.logger.debug('shimming mongodb-core.Server.prototype.command') | ||
span = agent.startSpan(ns + '.' + type, 'db', 'mongodb', 'query') | ||
span = ins.createSpan(ns + '.' + type, 'db', 'mongodb', 'query') | ||
if (span) { | ||
arguments[index] = wrappedCallback | ||
arguments[index] = ins.bindFunctionToRunContext(ins.currRunContext(), wrappedCallback) | ||
} | ||
@@ -83,5 +85,5 @@ } | ||
if (typeof cb === 'function') { | ||
span = agent.startSpan(ns + '.' + name, 'db', 'mongodb', 'query') | ||
span = ins.createSpan(ns + '.' + name, 'db', 'mongodb', 'query') | ||
if (span) { | ||
arguments[index] = wrappedCallback | ||
arguments[index] = ins.bindFunctionToRunContext(ins.currRunContext(), wrappedCallback) | ||
} | ||
@@ -113,6 +115,6 @@ } | ||
var spanName = `${this.ns}.${this.cmd.find ? 'find' : name}` | ||
span = agent.startSpan(spanName, 'db', 'mongodb', 'query') | ||
span = ins.createSpan(spanName, 'db', 'mongodb', 'query') | ||
} | ||
if (span) { | ||
arguments[0] = wrappedCallback | ||
arguments[0] = ins.bindFunctionToRunContext(ins.currRunContext(), wrappedCallback) | ||
if (name === 'next') { | ||
@@ -119,0 +121,0 @@ this[firstSpan] = true |
@@ -13,2 +13,5 @@ 'use strict' | ||
module.exports = function (mysql, agent, { version, enabled }) { | ||
if (!enabled) { | ||
return mysql | ||
} | ||
if (!semver.satisfies(version, '^2.0.0')) { | ||
@@ -25,4 +28,2 @@ agent.logger.debug('mysql version %s not supported - aborting...', version) | ||
if (!enabled) return mysql | ||
agent.logger.debug('shimming mysql.createConnection') | ||
@@ -80,3 +81,3 @@ shimmer.wrap(mysql, 'createConnection', wrapCreateConnection) | ||
arguments[0] = agent._instrumentation.bindFunction(function wrapedCallback (err, connection) { // eslint-disable-line handle-callback-err | ||
if (connection && enabled) wrapQueryable(connection, 'getConnection() > connection', agent) | ||
if (connection) wrapQueryable(connection, 'getConnection() > connection', agent) | ||
return cb.apply(this, arguments) | ||
@@ -83,0 +84,0 @@ }) |
@@ -15,2 +15,5 @@ 'use strict' | ||
module.exports = function (redis, agent, { version, enabled }) { | ||
if (!enabled) { | ||
return redis | ||
} | ||
if (!semver.satisfies(version, '>=2.0.0 <4.0.0')) { | ||
@@ -74,3 +77,3 @@ agent.logger.debug('redis version %s not supported - aborting...', version) | ||
agent.logger.debug({ command: command }, 'intercepted call to RedisClient.prototype.internal_send_command') | ||
const span = enabled && ins.createSpan(command.toUpperCase(), TYPE, SUBTYPE) | ||
const span = ins.createSpan(command.toUpperCase(), TYPE, SUBTYPE) | ||
if (!span) { | ||
@@ -107,3 +110,3 @@ return original.apply(this, arguments) | ||
agent.logger.debug({ command: command }, 'intercepted call to RedisClient.prototype.send_command') | ||
var span = enabled && ins.createSpan(command.toUpperCase(), TYPE, SUBTYPE) | ||
var span = ins.createSpan(command.toUpperCase(), TYPE, SUBTYPE) | ||
if (!span) { | ||
@@ -110,0 +113,0 @@ return original.apply(this, arguments) |
@@ -19,9 +19,15 @@ 'use strict' | ||
function Span (transaction, name, ...args) { | ||
const defaultChildOf = transaction._agent._instrumentation.currSpan() || transaction | ||
// new Span(transaction) | ||
// new Span(transaction, name?, opts?) | ||
// new Span(transaction, name?, type?, opts?) | ||
// new Span(transaction, name?, type?, subtype?, opts?) | ||
// new Span(transaction, name?, type?, subtype?, action?, opts?) | ||
function Span (transaction, ...args) { | ||
const opts = typeof args[args.length - 1] === 'object' | ||
? (args.pop() || {}) | ||
: {} | ||
const [name, ...tsaArgs] = args // "tsa" === Type, Subtype, Action | ||
if (!opts.childOf) { | ||
const defaultChildOf = transaction._agent._instrumentation.currSpan() || transaction | ||
opts.childOf = defaultChildOf | ||
@@ -33,4 +39,7 @@ opts.timer = defaultChildOf._timer | ||
GenericSpan.call(this, transaction._agent, ...args, opts) | ||
this._exitSpan = !!opts.exitSpan | ||
delete opts.exitSpan | ||
GenericSpan.call(this, transaction._agent, ...tsaArgs, opts) | ||
this._db = null | ||
@@ -37,0 +46,0 @@ this._http = null |
@@ -119,4 +119,4 @@ 'use strict' | ||
// Create a span on this transaction and make it the current span. | ||
Transaction.prototype.startSpan = function (...spanArgs) { | ||
const span = this.createSpan(...spanArgs) | ||
Transaction.prototype.startSpan = function (...args) { | ||
const span = this.createSpan(...args) | ||
if (span) { | ||
@@ -133,3 +133,3 @@ this._agent._instrumentation.supersedeWithSpanRunContext(span) | ||
// context of the calling code. Compare to `startSpan`. | ||
Transaction.prototype.createSpan = function (...spanArgs) { | ||
Transaction.prototype.createSpan = function (...args) { | ||
if (!this.sampled) { | ||
@@ -147,4 +147,19 @@ return null | ||
// Exit spans must not have child spans (unless of the same type and subtype). | ||
// https://github.com/elastic/apm/blob/master/specs/agents/tracing-spans.md#child-spans-of-exit-spans | ||
const opts = typeof args[args.length - 1] === 'object' | ||
? (args.pop() || {}) | ||
: {} | ||
const [_name, type, subtype] = args // eslint-disable-line no-unused-vars | ||
opts.childOf = opts.childOf || this._agent._instrumentation.currSpan() || this | ||
const childOf = opts.childOf | ||
if (childOf instanceof Span && childOf._exitSpan && | ||
!(childOf.type === type && childOf.subtype === subtype)) { | ||
this._agent.logger.trace({ exitSpanId: childOf.id, newSpanArgs: args }, | ||
'createSpan: drop child span of exit span') | ||
return null | ||
} | ||
this._builtSpans++ | ||
return new Span(this, ...spanArgs) | ||
return new Span(this, ...args, opts) | ||
} | ||
@@ -202,3 +217,3 @@ | ||
// add sample_rate to transaction | ||
// https://github.com/elastic/apm/blob/master/specs/agents/tracing-sampling.md | ||
// https://github.com/elastic/apm/blob/main/specs/agents/tracing-sampling.md | ||
// Only set sample_rate on transaction payload if a valid trace state | ||
@@ -205,0 +220,0 @@ // variable is set. |
@@ -6,6 +6,6 @@ 'use strict' | ||
// https://github.com/elastic/apm/blob/master/specs/agents/tracing-instrumentation-aws-lambda.md#deriving-cold-starts | ||
// https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-aws-lambda.md#deriving-cold-starts | ||
let isFirstRun = true | ||
// https://github.com/elastic/apm/blob/master/specs/agents/tracing-instrumentation-aws-lambda.md#overwriting-metadata | ||
// https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-aws-lambda.md#overwriting-metadata | ||
function getLambdaMetadata (context) { | ||
@@ -12,0 +12,0 @@ // E.g. 'arn:aws:lambda:us-west-2:123456789012:function:my-function:someAlias' |
@@ -13,3 +13,3 @@ 'use strict' | ||
// | ||
// Per https://github.com/elastic/apm/blob/master/specs/agents/logging.md | ||
// Per https://github.com/elastic/apm/blob/main/specs/agents/logging.md | ||
// the valid log levels are: | ||
@@ -16,0 +16,0 @@ // - trace |
@@ -46,2 +46,6 @@ 'use strict' | ||
supportsKeepingUnsampledTransaction () { | ||
return true | ||
} | ||
// Inherited from Writable, called in agent.js. | ||
@@ -48,0 +52,0 @@ destroy () {} |
@@ -27,3 +27,3 @@ 'use strict' | ||
// | ||
// https://github.com/elastic/apm/blob/master/specs/agents/tracing-sampling.md | ||
// https://github.com/elastic/apm/blob/main/specs/agents/tracing-sampling.md | ||
tracestate.setValue('s', 0) | ||
@@ -30,0 +30,0 @@ } |
{ | ||
"name": "elastic-apm-node", | ||
"version": "3.27.0", | ||
"version": "3.28.0", | ||
"description": "The official Elastic APM agent for Node.js", | ||
@@ -89,3 +89,3 @@ "main": "index.js", | ||
"core-util-is": "^1.0.2", | ||
"elastic-apm-http-client": "^10.3.0", | ||
"elastic-apm-http-client": "^10.4.0", | ||
"end-of-stream": "^1.4.4", | ||
@@ -122,3 +122,3 @@ "error-callsites": "^2.0.4", | ||
"@elastic/elasticsearch": "^7.15.0", | ||
"@elastic/elasticsearch-canary": "^8.0.0-canary.37", | ||
"@elastic/elasticsearch-canary": "^8.1.0-canary.2", | ||
"@hapi/hapi": "^20.1.2", | ||
@@ -125,0 +125,0 @@ "@koa/router": "^9.0.1", |
Sorry, the diff of this file is not supported yet
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
485448
11493