elastic-apm-node
Advanced tools
Comparing version 1.14.3 to 2.0.0
@@ -0,1 +1,17 @@ | ||
# 2.0.0 - 2018/11/14 | ||
* Breaking changes: | ||
* chore: remove support for Node.js 4 and 9 | ||
* chore: remove deprecated buildSpan function ([#642](https://github.com/elastic/apm-agent-nodejs/pull/642)) | ||
* feat: support APM Server intake API version 2 ([#465](https://github.com/elastic/apm-agent-nodejs/pull/465)) | ||
* feat: improved filtering function API ([#579](https://github.com/elastic/apm-agent-nodejs/pull/579)) | ||
* feat: replace double-quotes with underscores in tag names ([#666](https://github.com/elastic/apm-agent-nodejs/pull/666)) | ||
* feat(config): change config order ([#604](https://github.com/elastic/apm-agent-nodejs/pull/604)) | ||
* feat(config): support time suffixes ([#602](https://github.com/elastic/apm-agent-nodejs/pull/602)) | ||
* feat(config): stricter boolean parsing ([#613](https://github.com/elastic/apm-agent-nodejs/pull/613)) | ||
* feat: add support for Distributed Tracing ([#538](https://github.com/elastic/apm-agent-nodejs/pull/538)) | ||
* feat(transaction): add transaction.ensureParentId function ([#661](https://github.com/elastic/apm-agent-nodejs/pull/661)) | ||
* feat(config): support byte suffixes ([#601](https://github.com/elastic/apm-agent-nodejs/pull/601)) | ||
* feat(transaction): restructure span\_count and include total ([#553](https://github.com/elastic/apm-agent-nodejs/pull/553)) | ||
* perf: improve Async Hooks implementation ([#679](https://github.com/elastic/apm-agent-nodejs/pull/679)) | ||
# 1.14.3 - 2018/11/13 | ||
@@ -2,0 +18,0 @@ * fix(async\_hooks): more reliable cleanup ([#674](https://github.com/elastic/apm-agent-nodejs/pull/674)) |
111
lib/agent.js
'use strict' | ||
var crypto = require('crypto') | ||
var http = require('http') | ||
@@ -8,6 +9,4 @@ var parseUrl = require('url').parse | ||
var afterAll = require('after-all-results') | ||
var ElasticAPMHttpClient = require('elastic-apm-http-client') | ||
var isError = require('core-util-is').isError | ||
var ancestors = require('require-ancestors') | ||
var uuid = require('uuid') | ||
@@ -19,3 +18,2 @@ var config = require('./config') | ||
var parsers = require('./parsers') | ||
var request = require('./request') | ||
var stackman = require('./stackman') | ||
@@ -28,3 +26,2 @@ var symbols = require('./symbols') | ||
var version = require('../package').version | ||
var userAgent = 'elastic-apm-node/' + version | ||
@@ -37,3 +34,6 @@ module.exports = Agent | ||
this._instrumentation = new Instrumentation(this) | ||
this._filters = new Filters() | ||
this._errorFilters = new Filters() | ||
this._transactionFilters = new Filters() | ||
this._spanFilters = new Filters() | ||
this._transport = null | ||
@@ -59,2 +59,12 @@ this._conf = null | ||
Object.defineProperty(Agent.prototype, 'currentSpan', { | ||
get () { | ||
return this._instrumentation.currentSpan | ||
} | ||
}) | ||
Agent.prototype.destroy = function () { | ||
if (this._transport) this._transport.destroy() | ||
} | ||
Agent.prototype.startTransaction = function () { | ||
@@ -73,11 +83,5 @@ return this._instrumentation.startTransaction.apply(this._instrumentation, arguments) | ||
Agent.prototype.startSpan = function (name, type) { | ||
var span = this._instrumentation.buildSpan.apply(this._instrumentation) | ||
if (span) span.start(name, type) | ||
return span | ||
return this._instrumentation.startSpan.apply(this._instrumentation, arguments) | ||
} | ||
Agent.prototype.buildSpan = function () { | ||
return this._instrumentation.buildSpan.apply(this._instrumentation, arguments) | ||
} | ||
Agent.prototype._config = function (opts) { | ||
@@ -100,4 +104,7 @@ this._conf = config(opts) | ||
this._config(opts) | ||
this._filters.config(this._conf) | ||
if (this._conf.filterHttpHeaders) { | ||
this.addFilter(require('./filters/http-headers')) | ||
} | ||
if (!this._conf.active) { | ||
@@ -139,12 +146,6 @@ this.logger.info('Elastic APM agent is inactive due to configuration') | ||
this._transport = this._conf.transport(this._conf, this) | ||
this._instrumentation.start() | ||
this._httpClient = new ElasticAPMHttpClient({ | ||
secretToken: this._conf.secretToken, | ||
userAgent: userAgent, | ||
serverUrl: this._conf.serverUrl, | ||
rejectUnauthorized: this._conf.verifyServerCert, | ||
serverTimeout: this._conf.serverTimeout * 1000 | ||
}) | ||
Error.stackTraceLimit = this._conf.stackTraceLimit | ||
@@ -183,2 +184,8 @@ if (this._conf.captureExceptions) this.handleUncaughtExceptions() | ||
Agent.prototype.addFilter = function (fn) { | ||
this.addErrorFilter(fn) | ||
this.addTransactionFilter(fn) | ||
this.addSpanFilter(fn) | ||
} | ||
Agent.prototype.addErrorFilter = function (fn) { | ||
if (typeof fn !== 'function') { | ||
@@ -189,5 +196,23 @@ this.logger.error('Can\'t add filter of type %s', typeof fn) | ||
this._filters.add(fn) | ||
this._errorFilters.add(fn) | ||
} | ||
Agent.prototype.addTransactionFilter = function (fn) { | ||
if (typeof fn !== 'function') { | ||
this.logger.error('Can\'t add filter of type %s', typeof fn) | ||
return | ||
} | ||
this._transactionFilters.add(fn) | ||
} | ||
Agent.prototype.addSpanFilter = function (fn) { | ||
if (typeof fn !== 'function') { | ||
this.logger.error('Can\'t add filter of type %s', typeof fn) | ||
return | ||
} | ||
this._spanFilters.add(fn) | ||
} | ||
Agent.prototype.captureError = function (err, opts, cb) { | ||
@@ -198,4 +223,5 @@ if (typeof opts === 'function') return this.captureError(err, null, opts) | ||
var trans = this.currentTransaction | ||
var timestamp = new Date().toISOString() | ||
var id = uuid.v4() | ||
var span = this.currentSpan | ||
var timestamp = Date.now() * 1000 | ||
var context = (span || trans || {}).context || {} | ||
var req = opts && opts.request instanceof IncomingMessage | ||
@@ -228,3 +254,5 @@ ? opts.request | ||
function prepareError (error) { | ||
error.id = id | ||
error.id = crypto.randomBytes(16).toString('hex') | ||
error.parent_id = context.id | ||
error.trace_id = context.traceId | ||
error.timestamp = timestamp | ||
@@ -249,4 +277,5 @@ error.context = { | ||
} | ||
if (trans) error.transaction = { id: trans.id } | ||
if (trans) error.transaction_id = trans.id | ||
if (error.exception) { | ||
@@ -301,6 +330,21 @@ error.exception.handled = !opts || opts.handled | ||
function send (error) { | ||
agent.logger.info('logging error %s with Elastic APM', id) | ||
request.errors(agent, [error], (err) => { | ||
if (cb) cb(err, error.id) | ||
}) | ||
error = agent._errorFilters.process(error) | ||
if (!error) { | ||
agent.logger.debug('error ignored by filter %o', { id: error.id }) | ||
if (cb) cb(null, error.id) | ||
return | ||
} | ||
if (agent._transport) { | ||
agent.logger.info(`Sending error to Elastic APM`, { id: error.id }) | ||
agent._transport.sendError(error, function () { | ||
agent._transport.flush(function (err) { | ||
if (cb) cb(err, error.id) | ||
}) | ||
}) | ||
} else if (cb) { | ||
// TODO: Swallow this error just as it's done in agent.flush()? | ||
process.nextTick(cb.bind(null, new Error('cannot capture error before agent is started'), error.id)) | ||
} | ||
} | ||
@@ -331,3 +375,8 @@ } | ||
Agent.prototype.flush = function (cb) { | ||
this._instrumentation.flush(cb) | ||
if (this._transport) { | ||
this._transport.flush(cb) | ||
} else { | ||
this.logger.warn(new Error('cannot flush agent before it is started')) | ||
process.nextTick(cb) | ||
} | ||
} | ||
@@ -334,0 +383,0 @@ |
'use strict' | ||
var fs = require('fs') | ||
var os = require('os') | ||
var path = require('path') | ||
var consoleLogLevel = require('console-log-level') | ||
var normalizeBool = require('normalize-bool') | ||
var ElasticAPMHttpClient = require('elastic-apm-http-client') | ||
var readPkgUp = require('read-pkg-up') | ||
var truncate = require('unicode-byte-truncate') | ||
var version = require('../package').version | ||
var userAgent = 'elastic-apm-node/' + version | ||
config.INTAKE_STRING_MAX_SIZE = 1024 | ||
@@ -38,3 +40,4 @@ config.CAPTURE_ERROR_LOG_STACK_TRACES_NEVER = 'never' | ||
logLevel: 'info', | ||
hostname: os.hostname(), | ||
apiRequestSize: '750kb', | ||
apiRequestTime: '10s', | ||
stackTraceLimit: 50, | ||
@@ -47,3 +50,3 @@ captureExceptions: true, | ||
errorOnAbortedRequests: false, | ||
abortedErrorThreshold: 25000, | ||
abortedErrorThreshold: '25s', | ||
instrument: true, | ||
@@ -55,8 +58,6 @@ asyncHooks: true, | ||
sourceLinesSpanLibraryFrames: 0, | ||
errorMessageMaxLength: 2048, | ||
flushInterval: 10, | ||
errorMessageMaxLength: '2kb', | ||
transactionMaxSpans: 500, | ||
transactionSampleRate: 1.0, | ||
maxQueueSize: 100, | ||
serverTimeout: 30, | ||
serverTimeout: '30s', | ||
disableInstrumentations: [] | ||
@@ -74,2 +75,4 @@ } | ||
hostname: 'ELASTIC_APM_HOSTNAME', | ||
apiRequestSize: 'ELASTIC_APM_API_REQUEST_SIZE', | ||
apiRequestTime: 'ELASTIC_APM_API_REQUEST_TIME', | ||
frameworkName: 'ELASTIC_APM_FRAMEWORK_NAME', | ||
@@ -86,4 +89,2 @@ frameworkVersion: 'ELASTIC_APM_FRAMEWORK_VERSION', | ||
instrument: 'ELASTIC_APM_INSTRUMENT', | ||
flushInterval: 'ELASTIC_APM_FLUSH_INTERVAL', | ||
maxQueueSize: 'ELASTIC_APM_MAX_QUEUE_SIZE', | ||
asyncHooks: 'ELASTIC_APM_ASYNC_HOOKS', | ||
@@ -98,3 +99,4 @@ sourceLinesErrorAppFrames: 'ELASTIC_APM_SOURCE_LINES_ERROR_APP_FRAMES', | ||
serverTimeout: 'ELASTIC_APM_SERVER_TIMEOUT', | ||
disableInstrumentations: 'ELASTIC_APM_DISABLE_INSTRUMENTATIONS' | ||
disableInstrumentations: 'ELASTIC_APM_DISABLE_INSTRUMENTATIONS', | ||
payloadLogFile: 'ELASTIC_APM_PAYLOAD_LOG_FILE' | ||
} | ||
@@ -115,5 +117,2 @@ | ||
'stackTraceLimit', | ||
'abortedErrorThreshold', | ||
'flushInterval', | ||
'maxQueueSize', | ||
'sourceLinesErrorAppFrames', | ||
@@ -123,10 +122,18 @@ 'sourceLinesErrorLibraryFrames', | ||
'sourceLinesSpanLibraryFrames', | ||
'errorMessageMaxLength', | ||
'transactionMaxSpans', | ||
'transactionSampleRate', | ||
'transactionSampleRate' | ||
] | ||
var TIME_OPTS = [ | ||
'apiRequestTime', | ||
'abortedErrorThreshold', | ||
'serverTimeout' | ||
] | ||
var BYTES_OPTS = [ | ||
'apiRequestSize', | ||
'errorMessageMaxLength' | ||
] | ||
var MINUS_ONE_EQUAL_INFINITY = [ | ||
'maxQueueSize', | ||
'transactionMaxSpans' | ||
@@ -143,5 +150,5 @@ ] | ||
DEFAULTS, // default options | ||
readEnv(), // options read from environment variables | ||
confFile, // options read from elastic-apm-node.js config file | ||
opts // options passed in to agent.start() | ||
opts, // options passed in to agent.start() | ||
readEnv() // options read from environment variables | ||
) | ||
@@ -153,13 +160,82 @@ | ||
// NOTE: A logger will already exists if a custom logger was given to start() | ||
if (typeof opts.logger === 'undefined') { | ||
opts.logger = consoleLogLevel({ | ||
level: opts.logLevel | ||
}) | ||
} | ||
normalizeIgnoreOptions(opts) | ||
normalizeNumbers(opts) | ||
normalizeBytes(opts) | ||
normalizeArrays(opts) | ||
normalizeTime(opts) | ||
normalizeBools(opts) | ||
truncateOptions(opts) | ||
// NOTE: A logger will already exists if a custom logger was given to start() | ||
if (typeof opts.logger !== 'function') { | ||
opts.logger = consoleLogLevel({ | ||
level: opts.logLevel | ||
}) | ||
if (typeof opts.transport !== 'function') { | ||
opts.transport = function httpTransport (conf, agent) { | ||
var transport = new ElasticAPMHttpClient({ | ||
// metadata | ||
agentName: 'nodejs', | ||
agentVersion: version, | ||
serviceName: conf.serviceName, | ||
serviceVersion: conf.serviceVersion, | ||
frameworkName: conf.frameworkName, | ||
frameworkVersion: conf.frameworkVersion, | ||
hostname: conf.hostname, | ||
// Sanitize conf | ||
truncateKeywordsAt: config.INTAKE_STRING_MAX_SIZE, | ||
truncateErrorMessagesAt: conf.errorMessageMaxLength, | ||
// HTTP conf | ||
secretToken: conf.secretToken, | ||
userAgent: userAgent, | ||
serverUrl: conf.serverUrl, | ||
rejectUnauthorized: conf.verifyServerCert, | ||
serverTimeout: conf.serverTimeout * 1000, | ||
// Streaming conf | ||
size: conf.apiRequestSize, | ||
time: conf.apiRequestTime * 1000, | ||
// Debugging | ||
payloadLogFile: conf.payloadLogFile | ||
}) | ||
transport.on('error', err => { | ||
const haveAccepted = Number.isFinite(err.accepted) | ||
const haveErrors = Array.isArray(err.errors) | ||
let msg | ||
if (err.code === 404) { | ||
msg = 'APM Server responded with "404 Not Found". ' + | ||
'This might be because you\'re running an incompatible version of the APM Server. ' + | ||
'This agent only supports APM Server v6.5 and above. ' + | ||
'If you\'re using an older version of the APM Server, ' + | ||
'please downgrade this agent to version 1.x or upgrade the APM Server' | ||
} else if (err.code) { | ||
msg = `APM Server transport error (${err.code}): ${err.message}` | ||
} else { | ||
msg = `APM Server transport error: ${err.message}` | ||
} | ||
if (haveAccepted || haveErrors) { | ||
if (haveAccepted) msg += `\nAPM Server accepted ${err.accepted} events in the last request` | ||
if (haveErrors) { | ||
err.errors.forEach(error => { | ||
msg += `\nError: ${error.message}` | ||
if (error.document) msg += `\n Document: ${error.document}` | ||
}) | ||
} | ||
} else if (err.response) { | ||
msg += `\n${err.response}` | ||
} | ||
agent.logger.error(msg) | ||
}) | ||
return transport | ||
} | ||
} | ||
@@ -214,2 +290,14 @@ | ||
function normalizeBytes (opts) { | ||
BYTES_OPTS.forEach(function (key) { | ||
if (key in opts) opts[key] = bytes(String(opts[key])) | ||
}) | ||
} | ||
function normalizeTime (opts) { | ||
TIME_OPTS.forEach(function (key) { | ||
if (key in opts) opts[key] = toSeconds(String(opts[key])) | ||
}) | ||
} | ||
function maybeSplit (value) { | ||
@@ -227,3 +315,3 @@ return typeof value === 'string' ? value.split(',') : value | ||
BOOL_OPTS.forEach(function (key) { | ||
if (key in opts) opts[key] = normalizeBool(opts[key]) | ||
if (key in opts) opts[key] = strictBool(opts.logger, key, opts[key]) | ||
}) | ||
@@ -236,1 +324,60 @@ } | ||
} | ||
function bytes (input) { | ||
const matches = input.match(/^(\d+)(b|kb|mb|gb)$/i) | ||
if (!matches) return Number(input) | ||
const suffix = matches[2].toLowerCase() | ||
let value = Number(matches[1]) | ||
if (!suffix || suffix === 'b') { | ||
return value | ||
} | ||
value *= 1024 | ||
if (suffix === 'kb') { | ||
return value | ||
} | ||
value *= 1024 | ||
if (suffix === 'mb') { | ||
return value | ||
} | ||
value *= 1024 | ||
if (suffix === 'gb') { | ||
return value | ||
} | ||
} | ||
function toSeconds (value) { | ||
var matches = /^(-)?(\d+)(m|ms|s)?$/.exec(value) | ||
if (!matches) return null | ||
var negate = matches[1] | ||
var amount = Number(matches[2]) | ||
if (negate) amount = -amount | ||
var scale = matches[3] | ||
if (scale === 'm') { | ||
amount *= 60 | ||
} else if (scale === 'ms') { | ||
amount /= 1000 | ||
} | ||
return amount | ||
} | ||
function strictBool (logger, key, value) { | ||
if (typeof value === 'boolean') { | ||
return value | ||
} | ||
// This will return undefined for unknown inputs, resulting in them being skipped. | ||
switch (value) { | ||
case 'false': return false | ||
case 'true': return true | ||
default: { | ||
logger.warn('unrecognized boolean value "%s" for "%s"', value, key) | ||
} | ||
} | ||
} |
@@ -7,31 +7,52 @@ 'use strict' | ||
module.exports = function (ins) { | ||
const asyncHook = asyncHooks.createHook({ init, destroy }) | ||
const transactions = new Map() | ||
const asyncHook = asyncHooks.createHook({ init, before, destroy }) | ||
const contexts = new WeakMap() | ||
shimmer.wrap(ins, 'addEndedTransaction', function (addEndedTransaction) { | ||
return function wrappedAddEndedTransaction (transaction) { | ||
if (contexts.has(transaction)) { | ||
for (let asyncId of contexts.get(transaction)) { | ||
if (transactions.has(asyncId)) { | ||
transactions.delete(asyncId) | ||
} | ||
} | ||
contexts.delete(transaction) | ||
const activeTransactions = new Map() | ||
Object.defineProperty(ins, 'currentTransaction', { | ||
get () { | ||
const asyncId = asyncHooks.executionAsyncId() | ||
return activeTransactions.get(asyncId) || null | ||
}, | ||
set (trans) { | ||
const asyncId = asyncHooks.executionAsyncId() | ||
if (trans) { | ||
activeTransactions.set(asyncId, trans) | ||
} else { | ||
activeTransactions.delete(asyncId) | ||
} | ||
return addEndedTransaction.call(this, transaction) | ||
} | ||
}) | ||
Object.defineProperty(ins, 'currentTransaction', { | ||
const activeSpans = new Map() | ||
Object.defineProperty(ins, 'activeSpan', { | ||
get () { | ||
const asyncId = asyncHooks.executionAsyncId() | ||
return transactions.has(asyncId) ? transactions.get(asyncId) : null | ||
return activeSpans.get(asyncId) || null | ||
}, | ||
set (trans) { | ||
set (span) { | ||
const asyncId = asyncHooks.executionAsyncId() | ||
transactions.set(asyncId, trans) | ||
if (span) { | ||
activeSpans.set(asyncId, span) | ||
} else { | ||
activeSpans.delete(asyncId) | ||
} | ||
} | ||
}) | ||
shimmer.wrap(ins, 'addEndedTransaction', function (addEndedTransaction) { | ||
return function wrappedAddEndedTransaction (transaction) { | ||
const asyncIds = contexts.get(transaction) | ||
if (asyncIds) { | ||
for (const asyncId of asyncIds) { | ||
activeTransactions.delete(asyncId) | ||
activeSpans.delete(asyncId) | ||
} | ||
contexts.delete(transaction) | ||
} | ||
return addEndedTransaction.call(this, transaction) | ||
} | ||
}) | ||
asyncHook.enable() | ||
@@ -45,27 +66,38 @@ | ||
var transaction = ins.currentTransaction | ||
const transaction = ins.currentTransaction | ||
if (!transaction) return | ||
transactions.set(asyncId, transaction) | ||
activeTransactions.set(asyncId, transaction) | ||
// Track the context by the transaction | ||
if (!contexts.has(transaction)) { | ||
contexts.set(transaction, []) | ||
let asyncIds = contexts.get(transaction) | ||
if (!asyncIds) { | ||
asyncIds = [] | ||
contexts.set(transaction, asyncIds) | ||
} | ||
contexts.get(transaction).push(asyncId) | ||
asyncIds.push(asyncId) | ||
const span = ins.bindingSpan || ins.activeSpan | ||
if (span) activeSpans.set(asyncId, span) | ||
} | ||
function before (asyncId) { | ||
ins.bindingSpan = null | ||
} | ||
function destroy (asyncId) { | ||
if (!transactions.has(asyncId)) return // in case type === TIMERWRAP | ||
const span = activeSpans.get(asyncId) | ||
const transaction = span ? span.transaction : activeTransactions.get(asyncId) | ||
var transaction = transactions.get(asyncId) | ||
if (contexts.get(transaction)) { | ||
var list = contexts.get(transaction) | ||
var index = list.indexOf(asyncId) | ||
list.splice(index, 1) | ||
if (transaction) { | ||
const asyncIds = contexts.get(transaction) | ||
if (asyncIds) { | ||
const index = asyncIds.indexOf(asyncId) | ||
asyncIds.splice(index, 1) | ||
} | ||
} | ||
transactions.delete(asyncId) | ||
activeTransactions.delete(asyncId) | ||
activeSpans.delete(asyncId) | ||
} | ||
} |
@@ -22,3 +22,4 @@ 'use strict' | ||
} else { | ||
var trans = agent.startTransaction() | ||
var traceparent = req.headers['elastic-apm-traceparent'] | ||
var trans = agent.startTransaction(null, null, traceparent) | ||
trans.type = 'request' | ||
@@ -37,5 +38,5 @@ trans.req = req | ||
if (agent._conf.errorOnAbortedRequests && !trans.ended) { | ||
var duration = Date.now() - trans._timer.start | ||
if (duration > agent._conf.abortedErrorThreshold) { | ||
agent.captureError('Socket closed with active HTTP request (>' + (agent._conf.abortedErrorThreshold / 1000) + ' sec)', { | ||
var duration = trans._timer.elapsed() | ||
if (duration > (agent._conf.abortedErrorThreshold * 1000)) { | ||
agent.captureError('Socket closed with active HTTP request (>' + agent._conf.abortedErrorThreshold + ' sec)', { | ||
request: req, | ||
@@ -84,2 +85,12 @@ extra: { abortTime: duration } | ||
// NOTE: This will also stringify and parse URL instances | ||
// to a format which can be mixed into the options object. | ||
function ensureUrl (v) { | ||
if (typeof v === 'string' || v instanceof url.URL) { | ||
return url.parse(String(v)) | ||
} else { | ||
return v | ||
} | ||
} | ||
exports.traceOutgoingRequest = function (agent, moduleName) { | ||
@@ -90,4 +101,4 @@ var spanType = 'ext.' + moduleName + '.http' | ||
return function (orig) { | ||
return function () { | ||
var span = agent.buildSpan() | ||
return function (...args) { | ||
var span = agent.startSpan(null, spanType) | ||
var id = span && span.transaction.id | ||
@@ -97,4 +108,27 @@ | ||
var req = orig.apply(this, arguments) | ||
var options = {} | ||
var newArgs = [ options ] | ||
for (let arg of args) { | ||
if (typeof arg === 'function') { | ||
newArgs.push(arg) | ||
} else { | ||
Object.assign(options, ensureUrl(arg)) | ||
} | ||
} | ||
if (!options.headers) options.headers = {} | ||
// 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 || agent.currentTransaction | ||
if (parent && parent.context) { | ||
options.headers['elastic-apm-traceparent'] = parent.context.toString() | ||
} | ||
var req = orig.apply(this, newArgs) | ||
if (!span) return req | ||
if (req._headers.host === agent._conf.serverHost) { | ||
@@ -110,4 +144,3 @@ agent.logger.debug('ignore %s request to intake API %o', moduleName, { id: id }) | ||
var name = req.method + ' ' + req._headers.host + url.parse(req.path).pathname | ||
span.start(name, spanType) | ||
span.name = req.method + ' ' + req._headers.host + url.parse(req.path).pathname | ||
req.on('response', onresponse) | ||
@@ -114,0 +147,0 @@ |
@@ -6,8 +6,5 @@ 'use strict' | ||
var AsyncValuePromise = require('async-value-promise') | ||
var hook = require('require-in-the-middle') | ||
var semver = require('semver') | ||
var Queue = require('../queue') | ||
var request = require('../request') | ||
var Transaction = require('./transaction') | ||
@@ -50,6 +47,17 @@ var shimmer = require('./shimmer') | ||
this._agent = agent | ||
this._queue = null | ||
this._hook = null // this._hook is only exposed for testing purposes | ||
this._started = false | ||
this.currentTransaction = null | ||
// Span for binding callbacks | ||
this.bindingSpan = null | ||
// Span which is actively bound | ||
this.activeSpan = null | ||
Object.defineProperty(this, 'currentSpan', { | ||
get () { | ||
return this.bindingSpan || this.activeSpan | ||
} | ||
}) | ||
} | ||
@@ -65,17 +73,2 @@ | ||
var qopts = { | ||
flushInterval: this._agent._conf.flushInterval, | ||
maxQueueSize: this._agent._conf.maxQueueSize, | ||
logger: this._agent.logger | ||
} | ||
this._queue = new Queue(qopts, function onFlush (transactions, done) { | ||
AsyncValuePromise.all(transactions).then(function (transactions) { | ||
if (self._agent._conf.active && transactions.length > 0) { | ||
request.transactions(self._agent, transactions, done) | ||
} else { | ||
done() | ||
} | ||
}, done) | ||
}) | ||
if (this._agent._conf.asyncHooks && semver.gte(process.version, '8.2.0')) { | ||
@@ -117,28 +110,42 @@ require('./async-hooks')(this) | ||
Instrumentation.prototype.addEndedTransaction = function (transaction) { | ||
var agent = this._agent | ||
if (this._started) { | ||
var queue = this._queue | ||
var payload = agent._transactionFilters.process(transaction._encode()) | ||
if (!payload) return agent.logger.debug('transaction ignored by filter %o', { trans: transaction.id, trace: transaction.traceId }) | ||
agent.logger.debug('sending transaction %o', { trans: transaction.id, trace: transaction.traceId }) | ||
agent._transport.sendTransaction(payload) | ||
} else { | ||
agent.logger.debug('ignoring transaction %o', { trans: transaction.id, trace: transaction.traceId }) | ||
} | ||
} | ||
this._agent.logger.debug('adding transaction to queue %o', { id: transaction.id }) | ||
Instrumentation.prototype.addEndedSpan = function (span) { | ||
var agent = this._agent | ||
var payload = new AsyncValuePromise() | ||
if (this._started) { | ||
agent.logger.debug('encoding span %o', { span: span.id, parent: span.parentId, trace: span.traceId, name: span.name, type: span.type }) | ||
span._encode(function (err, payload) { | ||
if (err) { | ||
agent.logger.error('error encoding span %o', { span: span.id, parent: span.parentId, trace: span.traceId, name: span.name, type: span.type, error: err.message }) | ||
return | ||
} | ||
payload.catch(function (err) { | ||
this._agent.logger.error('error encoding transaction %s: %s', transaction.id, err.message) | ||
}) | ||
payload = agent._spanFilters.process(payload) | ||
// Add the transaction payload to the queue instead of the transation | ||
// object it self to free up the transaction for garbage collection | ||
transaction._encode(function (err, _payload) { | ||
if (err) payload.reject(err) | ||
else payload.resolve(_payload) | ||
if (!payload) { | ||
agent.logger.debug('span ignored by filter %o', { span: span.id, parent: span.parentId, trace: span.traceId, name: span.name, type: span.type }) | ||
return | ||
} | ||
agent.logger.debug('sending span %o', { span: span.id, parent: span.parentId, trace: span.traceId, name: span.name, type: span.type }) | ||
if (agent._transport) agent._transport.sendSpan(payload) | ||
}) | ||
queue.add(payload) | ||
} else { | ||
this._agent.logger.debug('ignoring transaction %o', { id: transaction.id }) | ||
agent.logger.debug('ignoring span %o', { span: span.id, parent: span.parentId, trace: span.traceId, name: span.name, type: span.type }) | ||
} | ||
} | ||
Instrumentation.prototype.startTransaction = function (name, type) { | ||
return new Transaction(this._agent, name, type) | ||
Instrumentation.prototype.startTransaction = function (name, type, traceparent) { | ||
return new Transaction(this._agent, name, type, traceparent) | ||
} | ||
@@ -172,3 +179,3 @@ | ||
Instrumentation.prototype.buildSpan = function () { | ||
Instrumentation.prototype.startSpan = function (name, type) { | ||
if (!this.currentTransaction) { | ||
@@ -179,3 +186,3 @@ this._agent.logger.debug('no active transaction found - cannot build new span') | ||
return this.currentTransaction.buildSpan() | ||
return this.currentTransaction.startSpan(name, type) | ||
} | ||
@@ -188,2 +195,3 @@ | ||
var trans = this.currentTransaction | ||
var span = this.currentSpan | ||
if (trans && !trans.sampled) { | ||
@@ -196,6 +204,8 @@ return original | ||
function elasticAPMCallbackWrapper () { | ||
var prev = ins.currentTransaction | ||
var prevTrans = ins.currentTransaction | ||
ins.currentTransaction = trans | ||
ins.bindingSpan = null | ||
ins.activeSpan = span | ||
var result = original.apply(this, arguments) | ||
ins.currentTransaction = prev | ||
ins.currentTransaction = prevTrans | ||
return result | ||
@@ -227,3 +237,4 @@ } | ||
wrong: this.currentTransaction ? this.currentTransaction.id : undefined, | ||
correct: trans.id | ||
correct: trans.id, | ||
trace: trans.traceId | ||
}) | ||
@@ -233,9 +244,1 @@ | ||
} | ||
Instrumentation.prototype.flush = function (cb) { | ||
if (this._queue) { | ||
this._queue.flush(cb) | ||
} else { | ||
process.nextTick(cb) | ||
} | ||
} |
@@ -26,3 +26,3 @@ 'use strict' | ||
return function wrappedConnect (callback) { | ||
const span = agent.buildSpan() | ||
const span = agent.startSpan('Cassandra: Connect', 'db.cassandra.connect') | ||
if (!span) { | ||
@@ -32,4 +32,2 @@ return original.apply(this, arguments) | ||
span.start('Cassandra: Connect', 'db.cassandra.connect') | ||
function resolve () { | ||
@@ -68,3 +66,3 @@ span.end() | ||
return function wrappedBatch (queries, options, callback) { | ||
const span = agent.buildSpan() | ||
const span = agent.startSpan('Cassandra: Batch query', 'db.cassandra.query') | ||
if (!span) { | ||
@@ -77,4 +75,6 @@ return original.apply(this, arguments) | ||
span.setDbContext({ statement: query, type: 'cassandra' }) | ||
span.start('Cassandra: Batch query', 'db.cassandra.query') | ||
span.setDbContext({ | ||
statement: query, | ||
type: 'cassandra' | ||
}) | ||
@@ -112,3 +112,3 @@ function resolve () { | ||
return function wrappedExecute (query, params, options, callback) { | ||
const span = agent.buildSpan() | ||
const span = agent.startSpan(null, 'db.cassandra.query') | ||
if (!span) { | ||
@@ -118,6 +118,4 @@ return original.apply(this, arguments) | ||
span.type = 'db.cassandra.query' | ||
span.setDbContext({ statement: query, type: 'cassandra' }) | ||
span.name = sqlSummary(query) | ||
span.start() | ||
@@ -155,3 +153,3 @@ function resolve () { | ||
return function wrappedEachRow (query, params, options, rowCallback, callback) { | ||
const span = agent.buildSpan() | ||
const span = agent.startSpan(null, 'db.cassandra.query') | ||
if (!span) { | ||
@@ -161,6 +159,4 @@ return original.apply(this, arguments) | ||
span.type = 'db.cassandra.query' | ||
span.setDbContext({ statement: query, type: 'cassandra' }) | ||
span.name = sqlSummary(query) | ||
span.start() | ||
@@ -167,0 +163,0 @@ // Wrap the callback |
@@ -17,3 +17,3 @@ 'use strict' | ||
return function wrappedRequest (params, cb) { | ||
var span = agent.buildSpan() | ||
var span = agent.startSpan(null, 'db.elasticsearch.request') | ||
var id = span && span.transaction.id | ||
@@ -27,3 +27,3 @@ var method = params && params.method | ||
if (span && method && path) { | ||
span.start('Elasticsearch: ' + method + ' ' + path, 'db.elasticsearch.request') | ||
span.name = `Elasticsearch: ${method} ${path}` | ||
@@ -30,0 +30,0 @@ if (query && searchRegexp.test(path)) { |
@@ -59,5 +59,4 @@ 'use strict' | ||
var trans = agent._instrumentation.currentTransaction | ||
var span = agent.buildSpan() | ||
var span = agent.startSpan('GraphQL: Unkown Query', 'db.graphql.execute') | ||
var id = span && span.transaction.id | ||
var spanName = 'GraphQL: Unkown Query' | ||
agent.logger.debug('intercepted call to graphql.graphql %o', { id: id }) | ||
@@ -80,3 +79,3 @@ | ||
var queries = extractDetails(documentAST, operationName).queries | ||
if (queries.length > 0) spanName = 'GraphQL: ' + queries.join(', ') | ||
if (queries.length > 0) span.name = 'GraphQL: ' + queries.join(', ') | ||
} | ||
@@ -90,3 +89,2 @@ } else { | ||
span.start(spanName, 'db.graphql.execute') | ||
var p = orig.apply(this, arguments) | ||
@@ -103,5 +101,4 @@ p.then(function () { | ||
var trans = agent._instrumentation.currentTransaction | ||
var span = agent.buildSpan() | ||
var span = agent.startSpan('GraphQL: Unkown Query', 'db.graphql.execute') | ||
var id = span && span.transaction.id | ||
var spanName = 'GraphQL: Unkown Query' | ||
agent.logger.debug('intercepted call to graphql.execute %o', { id: id }) | ||
@@ -120,3 +117,3 @@ | ||
operationName = operationName || (details.operation && details.operation.name && details.operation.name.value) | ||
if (queries.length > 0) spanName = 'GraphQL: ' + (operationName ? operationName + ' ' : '') + queries.join(', ') | ||
if (queries.length > 0) span.name = 'GraphQL: ' + (operationName ? operationName + ' ' : '') + queries.join(', ') | ||
@@ -132,3 +129,2 @@ if (trans._graphqlRoute) { | ||
span.start(spanName, 'db.graphql.execute') | ||
var p = orig.apply(this, arguments) | ||
@@ -135,0 +131,0 @@ if (typeof p.then === 'function') { |
@@ -14,3 +14,3 @@ 'use strict' | ||
return function wrappedTemplate (data) { | ||
var span = agent.buildSpan() | ||
var span = agent.startSpan('handlebars', 'template.handlebars.render') | ||
var id = span && span.transaction.id | ||
@@ -23,3 +23,2 @@ | ||
if (span) span.start('handlebars', 'template.handlebars.render') | ||
var ret = original.apply(this, arguments) | ||
@@ -34,3 +33,3 @@ if (span) span.end() | ||
return function wrappedCompile (input) { | ||
var span = agent.buildSpan() | ||
var span = agent.startSpan('handlebars', 'template.handlebars.compile') | ||
var id = span && span.transaction.id | ||
@@ -43,3 +42,2 @@ | ||
if (span) span.start('handlebars', 'template.handlebars.compile') | ||
var ret = original.apply(this, arguments) | ||
@@ -46,0 +44,0 @@ if (span) span.end() |
@@ -148,3 +148,3 @@ 'use strict' | ||
return function (headers) { | ||
var span = agent.buildSpan() | ||
var span = agent.startSpan(null, 'ext.http2') | ||
var id = span && span.transaction.id | ||
@@ -160,4 +160,3 @@ | ||
var path = url.parse(headers[':path']).pathname | ||
var name = headers[':method'] + ' ' + host + path | ||
span.start(name, 'ext.http2') | ||
span.name = headers[':method'] + ' ' + host + path | ||
@@ -164,0 +163,0 @@ req.on('end', () => { |
@@ -51,3 +51,3 @@ 'use strict' | ||
return function wrappedSendCommand (command) { | ||
var span = agent.buildSpan() | ||
var span = agent.startSpan(null, 'cache.redis') | ||
var id = span && span.transaction.id | ||
@@ -80,3 +80,3 @@ | ||
span.start(String(command.name).toUpperCase(), 'cache.redis') | ||
span.name = String(command.name).toUpperCase() | ||
} | ||
@@ -83,0 +83,0 @@ |
@@ -42,3 +42,3 @@ 'use strict' | ||
var cb = arguments[index] | ||
if (typeof cb === 'function' && (span = agent.buildSpan())) { | ||
if (typeof cb === 'function') { | ||
var type | ||
@@ -51,4 +51,6 @@ if (cmd.findAndModify) type = 'findAndModify' | ||
arguments[index] = wrappedCallback | ||
span.start(ns + '.' + type, 'db.mongodb.query') | ||
span = agent.startSpan(ns + '.' + type, 'db.mongodb.query') | ||
if (span) { | ||
arguments[index] = wrappedCallback | ||
} | ||
} | ||
@@ -78,5 +80,7 @@ } | ||
var cb = arguments[index] | ||
if (typeof cb === 'function' && (span = agent.buildSpan())) { | ||
arguments[index] = wrappedCallback | ||
span.start(ns + '.' + name, 'db.mongodb.query') | ||
if (typeof cb === 'function') { | ||
span = agent.startSpan(ns + '.' + name, 'db.mongodb.query') | ||
if (span) { | ||
arguments[index] = wrappedCallback | ||
} | ||
} | ||
@@ -105,5 +109,8 @@ } | ||
var cb = arguments[0] | ||
if (typeof cb === 'function' && (span = agent.buildSpan())) { | ||
arguments[0] = wrappedCallback | ||
span.start(this.ns + '.' + (this.cmd.find ? 'find' : name), 'db.mongodb.query') | ||
if (typeof cb === 'function') { | ||
var spanName = `${this.ns}.${this.cmd.find ? 'find' : name}` | ||
span = agent.startSpan(spanName, 'db.mongodb.query') | ||
if (span) { | ||
arguments[0] = wrappedCallback | ||
} | ||
} | ||
@@ -110,0 +117,0 @@ } |
@@ -91,3 +91,3 @@ 'use strict' | ||
return function wrappedQuery (sql, values, cb) { | ||
var span = agent.buildSpan() | ||
var span = agent.startSpan(null, 'db.mysql.query') | ||
var id = span && span.transaction.id | ||
@@ -100,4 +100,2 @@ var hasCallback = false | ||
if (span) { | ||
span.type = 'db.mysql.query' | ||
if (this[symbols.knexStackObj]) { | ||
@@ -141,3 +139,2 @@ span.customStackTrace(this[symbols.knexStackObj]) | ||
return function (event) { | ||
span.start() | ||
switch (event) { | ||
@@ -157,3 +154,2 @@ case 'error': | ||
hasCallback = true | ||
span.start() | ||
return function wrappedCallback () { | ||
@@ -160,0 +156,0 @@ span.end() |
@@ -23,3 +23,3 @@ 'use strict' | ||
return function wrappedQuery (sql, values, cb) { | ||
var span = enabled && agent.buildSpan() | ||
var span = enabled && agent.startSpan(null, 'db.mysql.query') | ||
var id = span && span.transaction.id | ||
@@ -30,4 +30,2 @@ var hasCallback = false | ||
if (span) { | ||
span.type = 'db.mysql.query' | ||
if (this[symbols.knexStackObj]) { | ||
@@ -70,9 +68,4 @@ span.customStackTrace(this[symbols.knexStackObj]) | ||
if (span) { | ||
var started = false | ||
shimmer.wrap(result, 'emit', function (original) { | ||
return function (event) { | ||
if (!started) { | ||
started = true | ||
span.start() | ||
} | ||
switch (event) { | ||
@@ -94,3 +87,2 @@ case 'error': | ||
hasCallback = true | ||
if (span) span.start() | ||
return agent._instrumentation.bindFunction(span ? wrappedCallback : cb) | ||
@@ -97,0 +89,0 @@ function wrappedCallback () { |
@@ -47,3 +47,3 @@ 'use strict' | ||
return function wrappedFunction (sql) { | ||
var span = agent.buildSpan() | ||
var span = agent.startSpan('SQL', 'db.postgresql.query') | ||
var id = span && span.transaction.id | ||
@@ -72,6 +72,5 @@ | ||
span.setDbContext({ statement: sql, type: 'sql' }) | ||
span.start(sqlSummary(sql), 'db.postgresql.query') | ||
span.name = sqlSummary(sql) | ||
} else { | ||
agent.logger.debug('unable to parse sql form pg module (type: %s)', typeof sql) | ||
span.start('SQL', 'db.postgresql.query') | ||
} | ||
@@ -78,0 +77,0 @@ |
@@ -35,3 +35,3 @@ 'use strict' | ||
return function wrappedInternalSendCommand (commandObj) { | ||
var span = enabled && agent.buildSpan() | ||
var span = enabled && agent.startSpan(null, 'cache.redis') | ||
var id = span && span.transaction.id | ||
@@ -44,3 +44,3 @@ var command = commandObj && commandObj.command | ||
commandObj.callback = makeWrappedCallback(span, commandObj.callback) | ||
if (span) span.start(String(command).toUpperCase(), 'cache.redis') | ||
if (span) span.name = String(command).toUpperCase() | ||
} | ||
@@ -54,3 +54,3 @@ | ||
return function wrappedSendCommand (command) { | ||
var span = enabled && agent.buildSpan() | ||
var span = enabled && agent.startSpan(null, 'cache.redis') | ||
var id = span && span.transaction.id | ||
@@ -76,3 +76,3 @@ var args = Array.prototype.slice.call(arguments) | ||
} | ||
if (span) span.start(String(command).toUpperCase(), 'cache.redis') | ||
if (span) span.name = String(command).toUpperCase() | ||
} | ||
@@ -79,0 +79,0 @@ |
@@ -42,3 +42,3 @@ 'use strict' | ||
Connection.prototype.makeRequest = function makeRequest (request) { | ||
const span = agent.buildSpan() | ||
const span = agent.startSpan(null, 'db.mssql.query') | ||
if (!span) { | ||
@@ -51,5 +51,4 @@ return originalMakeRequest.apply(this, arguments) | ||
const sql = (params.statement || params.stmt || {}).value | ||
const name = sqlSummary(sql) + (preparing ? ' (prepare)' : '') | ||
span.name = sqlSummary(sql) + (preparing ? ' (prepare)' : '') | ||
span.setDbContext({ statement: sql, type: 'sql' }) | ||
span.start(name, 'db.mssql.query') | ||
@@ -56,0 +55,0 @@ request.userCallback = wrapCallback(request.userCallback) |
@@ -21,3 +21,3 @@ 'use strict' | ||
return function wrappedSend () { | ||
var span = agent.buildSpan() | ||
var span = agent.startSpan('Send WebSocket Message', 'websocket.send') | ||
var id = span && span.transaction.id | ||
@@ -38,4 +38,2 @@ | ||
span.start('Send WebSocket Message', 'websocket.send') | ||
return orig.apply(this, args) | ||
@@ -42,0 +40,0 @@ |
@@ -14,60 +14,42 @@ 'use strict' | ||
function Span (transaction) { | ||
function Span (transaction, name, type) { | ||
this.transaction = transaction | ||
this.started = false | ||
this.truncated = false | ||
this.ended = false | ||
this.name = null | ||
this.type = null | ||
this._db = null | ||
this._timer = null | ||
this._stackObj = null | ||
this._agent = transaction._agent | ||
this._agent.logger.debug('init span %o', { id: this.transaction.id }) | ||
} | ||
var current = this._agent._instrumentation.activeSpan || transaction | ||
this.context = current.context.child() | ||
Span.prototype.start = function (name, type) { | ||
if (this.started) { | ||
this._agent.logger.debug('tried to call span.start() on already started span %o', { id: this.transaction.id, name: this.name, type: this.type }) | ||
return | ||
} | ||
this.name = name || 'unnamed' | ||
this.type = type || 'custom' | ||
this.started = true | ||
this.name = name || this.name || 'unnamed' | ||
this.type = type || this.type || 'custom' | ||
this._agent._instrumentation.bindingSpan = this | ||
if (this._agent._conf.captureSpanStackTraces && !this._stackObj) { | ||
if (this._agent._conf.captureSpanStackTraces) { | ||
this._recordStackTrace() | ||
} | ||
this._timer = new Timer() | ||
this._timer = new Timer(this.transaction._timer) | ||
this.timestamp = this._timer.start | ||
this._agent.logger.debug('start span %o', { id: this.transaction.id, name: name, type: type }) | ||
this._agent.logger.debug('start span %o', { span: this.id, parent: this.parentId, trace: this.traceId, name: name, type: type }) | ||
} | ||
Object.defineProperty(Span.prototype, 'id', { | ||
get () { | ||
return this.context.id | ||
} | ||
}) | ||
Span.prototype.customStackTrace = function (stackObj) { | ||
this._agent.logger.debug('applying custom stack trace to span %o', { id: this.transaction.id }) | ||
this._agent.logger.debug('applying custom stack trace to span %o', { span: this.id, parent: this.parentId, trace: this.traceId }) | ||
this._recordStackTrace(stackObj) | ||
} | ||
Span.prototype.truncate = function () { | ||
if (!this.started) { | ||
this._agent.logger.debug('tried to truncate non-started span - ignoring %o', { id: this.transaction.id, name: this.name, type: this.type }) | ||
return | ||
} else if (this.ended) { | ||
this._agent.logger.debug('tried to truncate already ended span - ignoring %o', { id: this.transaction.id, name: this.name, type: this.type }) | ||
return | ||
} | ||
this.truncated = true | ||
this.end() | ||
} | ||
Span.prototype.end = function () { | ||
if (!this.started) { | ||
this._agent.logger.debug('tried to call span.end() on un-started span %o', { id: this.transaction.id, name: this.name, type: this.type }) | ||
if (this.ended) { | ||
this._agent.logger.debug('tried to call span.end() on already ended span %o', { span: this.id, parent: this.parentId, trace: this.traceId, name: this.name, type: this.type }) | ||
return | ||
} else if (this.ended) { | ||
this._agent.logger.debug('tried to call span.end() on already ended span %o', { id: this.transaction.id, name: this.name, type: this.type }) | ||
return | ||
} | ||
@@ -79,4 +61,4 @@ | ||
this.ended = true | ||
this._agent.logger.debug('ended span %o', { id: this.transaction.id, name: this.name, type: this.type, truncated: this.truncated }) | ||
this.transaction._recordEndedSpan(this) | ||
this._agent.logger.debug('ended span %o', { span: this.id, parent: this.parentId, trace: this.traceId, name: this.name, type: this.type }) | ||
this._agent._instrumentation.addEndedSpan(this) | ||
} | ||
@@ -86,18 +68,9 @@ | ||
if (!this.ended) { | ||
this._agent.logger.debug('tried to call span.duration() on un-ended span %o', { id: this.transaction.id, name: this.name, type: this.type }) | ||
this._agent.logger.debug('tried to call span.duration() on un-ended span %o', { span: this.id, parent: this.parentId, trace: this.traceId, name: this.name, type: this.type }) | ||
return null | ||
} | ||
return this._timer.duration() | ||
return this._timer.duration | ||
} | ||
Span.prototype.offsetTime = function () { | ||
if (!this.started) { | ||
this._agent.logger.debug('tried to call span.offsetTime() for un-started span %o', { id: this.transaction.id, name: this.name, type: this.type }) | ||
return null | ||
} | ||
return this._timer.offset(this.transaction._timer) | ||
} | ||
Span.prototype.setDbContext = function (context) { | ||
@@ -111,3 +84,3 @@ if (!context) return | ||
obj = {} | ||
Error.captureStackTrace(obj, Span.prototype.start) | ||
Error.captureStackTrace(obj, Span) | ||
} | ||
@@ -126,3 +99,3 @@ | ||
if (err || !callsites) { | ||
self._agent.logger.debug('could not capture stack trace for span %o', { id: self.transaction.id, name: self.name, type: self.type, err: err && err.message }) | ||
self._agent.logger.debug('could not capture stack trace for span %o', { span: self.id, parent: self.parentId, trace: self.traceId, name: self.name, type: self.type, err: err && err.message }) | ||
stack.reject(err) | ||
@@ -147,3 +120,2 @@ return | ||
if (!this.started) return cb(new Error('cannot encode un-started span')) | ||
if (!this.ended) return cb(new Error('cannot encode un-ended span')) | ||
@@ -162,9 +134,13 @@ | ||
if (err) { | ||
self._agent.logger.warn('could not capture stack trace for span %o', { id: self.transaction.id, name: self.name, type: self.type, err: err.message }) | ||
self._agent.logger.warn('could not capture stack trace for span %o', { span: self.id, parent: self.parentId, trace: self.traceId, name: self.name, type: self.type, err: err.message }) | ||
} | ||
var payload = { | ||
id: self.context.id, | ||
transaction_id: self.transaction.id, | ||
parent_id: self.context.parentId, | ||
trace_id: self.context.traceId, | ||
name: self.name, | ||
type: self.truncated ? self.type + '.truncated' : self.type, | ||
start: self.offsetTime(), | ||
type: self.type, | ||
timestamp: self.timestamp, | ||
duration: self.duration() | ||
@@ -171,0 +147,0 @@ } |
'use strict' | ||
var microtime = require('relative-microtime') | ||
module.exports = Timer | ||
function Timer () { | ||
this.ended = false | ||
this.start = Date.now() | ||
this._hrtime = process.hrtime() | ||
this._diff = null | ||
function Timer (timer) { | ||
this._timer = timer ? timer._timer : microtime() | ||
this.start = this._timer() | ||
this.duration = null | ||
} | ||
Timer.prototype.end = function () { | ||
if (this.ended) return | ||
this._diff = process.hrtime(this._hrtime) | ||
this.ended = true | ||
if (this.duration !== null) return | ||
this.duration = this.elapsed() | ||
} | ||
Timer.prototype.duration = function () { | ||
if (!this.ended) return null | ||
var ns = this._diff[0] * 1e9 + this._diff[1] | ||
return ns / 1e6 | ||
Timer.prototype.elapsed = function () { | ||
// durations are in milliseconds ¯\_(ツ)_/¯ | ||
return (this._timer() - this.start) / 1000 | ||
} | ||
Timer.prototype.offset = function (timer) { | ||
var a = timer._hrtime | ||
var b = this._hrtime | ||
var ns = (b[0] - a[0]) * 1e9 + (b[1] - a[1]) | ||
return ns / 1e6 | ||
} |
'use strict' | ||
var afterAll = require('after-all-results') | ||
var truncate = require('unicode-byte-truncate') | ||
var uuid = require('uuid') | ||
var config = require('../config') | ||
var TraceContext = require('./trace-context') | ||
var getPathFromRequest = require('./express-utils').getPathFromRequest | ||
@@ -16,40 +15,11 @@ var parsers = require('../parsers') | ||
function Transaction (agent, name, type) { | ||
Object.defineProperty(this, 'name', { | ||
configurable: true, | ||
enumerable: true, | ||
get () { | ||
// Fall back to a somewhat useful name in case no _defaultName is set. | ||
// This might happen if res.writeHead wasn't called. | ||
return this._customName || | ||
this._defaultName || | ||
(this.req ? this.req.method + ' unknown route (unnamed)' : 'unnamed') | ||
}, | ||
set (name) { | ||
if (this.ended) { | ||
agent.logger.debug('tried to set transaction.name on already ended transaction %o', { id: this.id }) | ||
return | ||
} | ||
agent.logger.debug('setting transaction name %o', { id: this.id, name: name }) | ||
this._customName = name | ||
} | ||
}) | ||
function Transaction (agent, name, type, traceparent) { | ||
this.context = TraceContext.startOrResume(traceparent, agent._conf) | ||
const verb = this.context.parentId ? 'continue' : 'start' | ||
agent.logger.debug('%s trace %o', verb, { trans: this.id, parent: this.parentId, trace: this.traceId, name: name, type: type }) | ||
Object.defineProperty(this, 'result', { | ||
configurable: true, | ||
enumerable: true, | ||
get () { | ||
return this._result | ||
}, | ||
set (result) { | ||
if (this.ended) { | ||
agent.logger.debug('tried to set transaction.result on already ended transaction %o', { id: this.id }) | ||
return | ||
} | ||
agent.logger.debug('setting transaction result %o', { id: this.id, result: result }) | ||
this._result = result | ||
} | ||
}) | ||
this._agent = agent | ||
this._agent._instrumentation.currentTransaction = this | ||
this._agent._instrumentation.activeSpan = null | ||
this.id = uuid.v4() | ||
this._defaultName = name || '' | ||
@@ -61,19 +31,76 @@ this._customName = '' | ||
this.type = type || 'custom' | ||
this.result = 'success' | ||
this.spans = [] | ||
this._builtSpans = [] | ||
this._result = 'success' | ||
this._builtSpans = 0 | ||
this._droppedSpans = 0 | ||
this._contextLost = false // TODO: Send this up to the server some how | ||
this.ended = false | ||
this._abortTime = 0 | ||
this._agent = agent | ||
this._agent._instrumentation.currentTransaction = this | ||
this._timer = new Timer() | ||
this.timestamp = this._timer.start | ||
} | ||
// Random sampling | ||
this.sampled = Math.random() <= this._agent._conf.transactionSampleRate | ||
Object.defineProperty(Transaction.prototype, 'name', { | ||
configurable: true, | ||
enumerable: true, | ||
get () { | ||
// Fall back to a somewhat useful name in case no _defaultName is set. | ||
// This might happen if res.writeHead wasn't called. | ||
return this._customName || | ||
this._defaultName || | ||
(this.req ? this.req.method + ' unknown route (unnamed)' : 'unnamed') | ||
}, | ||
set (name) { | ||
if (this.ended) { | ||
this._agent.logger.debug('tried to set transaction.name on already ended transaction %o', { trans: this.id, parent: this.parentId, trace: this.traceId }) | ||
return | ||
} | ||
this._agent.logger.debug('setting transaction name %o', { trans: this.id, parent: this.parentId, trace: this.traceId, name: name }) | ||
this._customName = name | ||
} | ||
}) | ||
agent.logger.debug('start transaction %o', { id: this.id, name: name, type: type }) | ||
Object.defineProperty(Transaction.prototype, 'result', { | ||
configurable: true, | ||
enumerable: true, | ||
get () { | ||
return this._result | ||
}, | ||
set (result) { | ||
if (this.ended) { | ||
this._agent.logger.debug('tried to set transaction.result on already ended transaction %o', { trans: this.id, parent: this.parentId, trace: this.traceId }) | ||
return | ||
} | ||
this._agent.logger.debug('setting transaction result %o', { trans: this.id, parent: this.parentId, trace: this.traceId, result: result }) | ||
this._result = result | ||
} | ||
}) | ||
this._timer = new Timer() | ||
} | ||
Object.defineProperty(Transaction.prototype, 'id', { | ||
enumerable: true, | ||
get () { | ||
return this.context.id | ||
} | ||
}) | ||
Object.defineProperty(Transaction.prototype, 'traceId', { | ||
enumerable: true, | ||
get () { | ||
return this.context.traceId | ||
} | ||
}) | ||
Object.defineProperty(Transaction.prototype, 'parentId', { | ||
enumerable: true, | ||
get () { | ||
return this.context.parentId | ||
} | ||
}) | ||
Object.defineProperty(Transaction.prototype, 'sampled', { | ||
enumerable: true, | ||
get () { | ||
return this.context.sampled | ||
} | ||
}) | ||
Transaction.prototype.setUserContext = function (context) { | ||
@@ -92,3 +119,3 @@ if (!context) return | ||
if (!this._tags) this._tags = {} | ||
var skey = key.replace(/[.*]/g, '_') | ||
var skey = key.replace(/[.*"]/g, '_') | ||
if (key !== skey) { | ||
@@ -112,3 +139,3 @@ this._agent.logger.warn('Illegal characters used in tag key: %s', key) | ||
Transaction.prototype.buildSpan = function () { | ||
Transaction.prototype.startSpan = function (name, type) { | ||
if (!this.sampled) { | ||
@@ -119,13 +146,12 @@ return null | ||
if (this.ended) { | ||
this._agent.logger.debug('transaction already ended - cannot build new span %o', { id: this.id }) | ||
this._agent.logger.debug('transaction already ended - cannot build new span %o', { trans: this.id, parent: this.parentId, trace: this.traceId }) // TODO: Should this be supported in the new API? | ||
return null | ||
} | ||
if (this._builtSpans.length >= this._agent._conf.transactionMaxSpans) { | ||
if (this._builtSpans >= this._agent._conf.transactionMaxSpans) { | ||
this._droppedSpans++ | ||
return null | ||
} | ||
this._builtSpans++ | ||
var span = new Span(this) | ||
this._builtSpans.push(span) | ||
return span | ||
return new Span(this, name, type) | ||
} | ||
@@ -136,10 +162,14 @@ | ||
id: this.id, | ||
trace_id: this.traceId, | ||
parent_id: this.parentId, | ||
name: this.name, | ||
type: this.type, | ||
duration: this.duration(), | ||
timestamp: new Date(this._timer.start).toISOString(), | ||
timestamp: this.timestamp, | ||
result: String(this.result), | ||
sampled: this.sampled, | ||
context: null, | ||
spans: null | ||
context: undefined, | ||
span_count: { | ||
started: this._builtSpans | ||
} | ||
} | ||
@@ -160,7 +190,3 @@ | ||
if (this._droppedSpans > 0) { | ||
payload.span_count = { | ||
dropped: { | ||
total: this._droppedSpans | ||
} | ||
} | ||
payload.span_count.dropped = this._droppedSpans | ||
} | ||
@@ -181,21 +207,9 @@ | ||
Transaction.prototype._encode = function (cb) { | ||
var self = this | ||
Transaction.prototype._encode = function () { | ||
if (!this.ended) { | ||
this._agent.logger.error('cannot encode un-ended transaction: %o', { trans: this.id, parent: this.parentId, trace: this.traceId }) | ||
return null | ||
} | ||
if (!this.ended) return cb(new Error('cannot encode un-ended transaction')) | ||
var payload = this.toJSON() | ||
var next = afterAll(function (err, spans) { | ||
if (err) return cb(err) | ||
if (self.sampled) { | ||
payload.spans = spans | ||
} | ||
cb(null, payload) | ||
}) | ||
this.spans.forEach(function (span) { | ||
span._encode(next()) | ||
}) | ||
return this.toJSON() | ||
} | ||
@@ -205,11 +219,11 @@ | ||
if (!this.ended) { | ||
this._agent.logger.debug('tried to call duration() on un-ended transaction %o', { id: this.id, name: this.name, type: this.type }) | ||
this._agent.logger.debug('tried to call duration() on un-ended transaction %o', { trans: this.id, parent: this.parentId, trace: this.traceId, name: this.name, type: this.type }) | ||
return null | ||
} | ||
return this._timer.duration() | ||
return this._timer.duration | ||
} | ||
Transaction.prototype.setDefaultName = function (name) { | ||
this._agent.logger.debug('setting default transaction name: %s %o', name, { id: this.id }) | ||
this._agent.logger.debug('setting default transaction name: %s %o', name, { trans: this.id, parent: this.parentId, trace: this.traceId }) | ||
this._defaultName = name | ||
@@ -230,3 +244,5 @@ } | ||
mountstack: req[symbols.expressMountStack] ? req[symbols.expressMountStack].length : false, | ||
id: this.id | ||
trans: this.id, | ||
parent: this.parentId, | ||
trace: this.traceId | ||
}) | ||
@@ -239,5 +255,9 @@ path = 'unknown route' | ||
Transaction.prototype.ensureParentId = function () { | ||
return this.context.ensureParentId() | ||
} | ||
Transaction.prototype.end = function (result) { | ||
if (this.ended) { | ||
this._agent.logger.debug('tried to call transaction.end() on already ended transaction %o', { id: this.id }) | ||
this._agent.logger.debug('tried to call transaction.end() on already ended transaction %o', { trans: this.id, parent: this.parentId, trace: this.traceId }) | ||
return | ||
@@ -252,7 +272,2 @@ } | ||
this._builtSpans.forEach(function (span) { | ||
if (span.ended || !span.started) return | ||
span.truncate() | ||
}) | ||
this._timer.end() | ||
@@ -269,20 +284,11 @@ this.ended = true | ||
if (!trans) { | ||
this._agent.logger.debug('WARNING: no currentTransaction found %o', { current: trans, spans: this.spans.length, id: this.id }) | ||
this.spans = [] | ||
this._agent.logger.debug('WARNING: no currentTransaction found %o', { current: trans, spans: this._builtSpans, trans: this.id, parent: this.parentId, trace: this.traceId }) | ||
this._contextLost = true | ||
} else if (trans !== this) { | ||
this._agent.logger.debug('WARNING: transaction is out of sync %o', { spans: this.spans.length, id: this.id, other: trans.id }) | ||
this.spans = [] | ||
this._agent.logger.debug('WARNING: transaction is out of sync %o', { other: trans.id, spans: this._builtSpans, trans: this.id, parent: this.parentId, trace: this.traceId }) | ||
this._contextLost = true | ||
} | ||
this._agent._instrumentation.addEndedTransaction(this) | ||
this._agent.logger.debug('ended transaction %o', { id: this.id, type: this.type, result: this.result, name: this.name }) | ||
this._agent.logger.debug('ended transaction %o', { trans: this.id, parent: this.parentId, trace: this.traceId, type: this.type, result: this.result, name: this.name }) | ||
} | ||
Transaction.prototype._recordEndedSpan = function (span) { | ||
if (this.ended) { | ||
this._agent.logger.debug('Can\'t record ended span after parent transaction have ended - ignoring %o', { id: this.id, span: span.name }) | ||
return | ||
} | ||
this.spans.push(span) | ||
} |
{ | ||
"name": "elastic-apm-node", | ||
"version": "1.14.3", | ||
"version": "2.0.0", | ||
"description": "The official Elastic APM agent for Node.js", | ||
@@ -72,76 +72,76 @@ "main": "index.js", | ||
"async-value-promise": "^1.1.1", | ||
"basic-auth": "^2.0.0", | ||
"basic-auth": "^2.0.1", | ||
"console-log-level": "^1.4.0", | ||
"cookie": "^0.3.1", | ||
"core-util-is": "^1.0.2", | ||
"elastic-apm-http-client": "^5.2.1", | ||
"elastic-apm-http-client": "^6.0.1", | ||
"end-of-stream": "^1.4.1", | ||
"fast-safe-stringify": "^2.0.4", | ||
"fast-safe-stringify": "^2.0.6", | ||
"http-headers": "^3.0.2", | ||
"is-native": "^1.0.1", | ||
"normalize-bool": "^1.0.0", | ||
"original-url": "^1.2.1", | ||
"read-pkg-up": "^3.0.0", | ||
"original-url": "^1.2.2", | ||
"read-pkg-up": "^4.0.0", | ||
"redact-secrets": "^1.0.0", | ||
"relative-microtime": "^2.0.0", | ||
"require-ancestors": "^1.0.0", | ||
"require-in-the-middle": "^3.1.0", | ||
"semver": "^5.5.0", | ||
"semver": "^5.6.0", | ||
"set-cookie-serde": "^1.0.0", | ||
"sql-summary": "^1.0.1", | ||
"stackman": "^3.0.2", | ||
"unicode-byte-truncate": "^1.0.0", | ||
"uuid": "^3.2.1" | ||
"unicode-byte-truncate": "^1.0.0" | ||
}, | ||
"devDependencies": { | ||
"@commitlint/cli": "^7.0.0", | ||
"@commitlint/config-conventional": "^7.0.1", | ||
"@commitlint/travis-cli": "^7.0.0", | ||
"apollo-server-express": "^2.0.2", | ||
"bluebird": "^3.4.6", | ||
"@commitlint/cli": "^7.2.1", | ||
"@commitlint/config-conventional": "^7.1.2", | ||
"@commitlint/travis-cli": "^7.2.1", | ||
"apollo-server-express": "^2.1.0", | ||
"bluebird": "^3.5.2", | ||
"cassandra-driver": "^3.5.0", | ||
"connect": "^3.6.3", | ||
"dependency-check": "^2.10.1", | ||
"elasticsearch": "^15.0.0", | ||
"express": "^4.14.0", | ||
"connect": "^3.6.6", | ||
"dependency-check": "^3.2.1", | ||
"elasticsearch": "^15.1.1", | ||
"express": "^4.16.4", | ||
"express-graphql": "^0.6.12", | ||
"express-queue": "0.0.12", | ||
"express-queue": "^0.0.12", | ||
"finalhandler": "^1.1.1", | ||
"generic-pool": "^3.1.5", | ||
"get-port": "^3.2.0", | ||
"got": "^9.0.0", | ||
"generic-pool": "^3.4.2", | ||
"get-port": "^4.0.0", | ||
"got": "^9.2.2", | ||
"graphql": "^0.13.2", | ||
"handlebars": "^4.0.11", | ||
"hapi": "^17.2.2", | ||
"handlebars": "^4.0.12", | ||
"hapi": "^17.6.0", | ||
"https-pem": "^2.0.0", | ||
"inquirer": "^0.12.0", | ||
"ioredis": "^3.0.0", | ||
"is-my-json-valid": "^2.17.2", | ||
"json-schema-ref-parser": "^5.0.3", | ||
"knex": "^0.15.0", | ||
"koa": "^2.2.0", | ||
"koa-router": "^7.1.1", | ||
"mimic-response": "^1.0.0", | ||
"mkdirp": "^0.5.0", | ||
"mongodb-core": "^3.0.2", | ||
"mysql": "^2.14.1", | ||
"is-my-json-valid": "^2.19.0", | ||
"json-schema-ref-parser": "^6.0.1", | ||
"knex": "^0.15.2", | ||
"koa": "^2.5.3", | ||
"koa-router": "^7.4.0", | ||
"mimic-response": "^1.0.1", | ||
"mkdirp": "^0.5.1", | ||
"mongodb-core": "^3.1.7", | ||
"mysql": "^2.16.0", | ||
"mysql2": "^1.6.3", | ||
"nyc": "^12.0.2", | ||
"ndjson": "^1.5.0", | ||
"nyc": "^13.1.0", | ||
"once": "^1.4.0", | ||
"p-finally": "^1.0.0", | ||
"pg": "^7.1.0", | ||
"redis": "^2.6.3", | ||
"request": "^2.86.0", | ||
"pg": "^7.5.0", | ||
"redis": "^2.8.0", | ||
"request": "^2.88.0", | ||
"restify": "^7.2.1", | ||
"restify-clients": "^2.0.2", | ||
"restify-clients": "^2.6.2", | ||
"rimraf": "^2.6.2", | ||
"send": "^0.16.1", | ||
"send": "^0.16.2", | ||
"standard": "^12.0.1", | ||
"tape": "^4.8.0", | ||
"tape": "^4.9.1", | ||
"tedious": "^2.6.1", | ||
"test-all-versions": "^3.3.3", | ||
"thunky": "^1.0.2", | ||
"untildify": "^3.0.2", | ||
"thunky": "^1.0.3", | ||
"untildify": "^3.0.3", | ||
"util.promisify": "^1.0.0", | ||
"wait-on": "^2.1.2", | ||
"ws": "^6.0.0" | ||
"wait-on": "^3.1.0", | ||
"ws": "^6.1.0" | ||
}, | ||
@@ -164,5 +164,5 @@ "greenkeeper": { | ||
"coordinates": [ | ||
55.778238, | ||
12.593177 | ||
55.778239, | ||
12.593173 | ||
] | ||
} |
@@ -9,3 +9,3 @@ # Elastic APM Node.js Agent | ||
[![npm](https://img.shields.io/npm/v/elastic-apm-node.svg)](https://www.npmjs.com/package/elastic-apm-node) | ||
[![Build status](https://travis-ci.org/elastic/apm-agent-nodejs.svg?branch=1.x)](https://travis-ci.org/elastic/apm-agent-nodejs) | ||
[![Build status](https://travis-ci.org/elastic/apm-agent-nodejs.svg?branch=2.x)](https://travis-ci.org/elastic/apm-agent-nodejs) | ||
[![Standard - JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/standard/standard) | ||
@@ -23,3 +23,5 @@ | ||
make sure you have the prerequisites in place first. | ||
This agent is compatible with [APM Server](https://github.com/elastic/apm-server) v6.2 and above. | ||
This agent is compatible with [APM Server](https://github.com/elastic/apm-server) v6.5 and above. | ||
For support for previous releases of the APM Server, | ||
use version [1.x](https://github.com/elastic/apm-agent-nodejs/tree/1.x) of the agent. | ||
For details see [Getting Started with Elastic APM](https://www.elastic.co/guide/en/apm/get-started) | ||
@@ -54,5 +56,10 @@ | ||
To ease development, | ||
set the environment variable `DEBUG_PAYLOAD=1` to have the agent dump the JSON payload sent to the APM Server to a temporary file on your local harddrive. | ||
To see what data is being sent to the APM Server, | ||
use the environment variable `ELASTIC_APM_PAYLOAD_LOG_FILE` (or the config option `payloadLogFile`) to speicfy a log file, | ||
e.g: | ||
``` | ||
ELASTIC_APM_PAYLOAD_LOG_FILE=/tmp/payload.ndjson | ||
``` | ||
Please see [TESTING.md](TESTING.md) for instructions on how to run the test suite. | ||
@@ -59,0 +66,0 @@ |
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
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
181516
22
4329
69
14
52
+ Addedrelative-microtime@^2.0.0
+ Addedelastic-apm-http-client@6.0.1(transitive)
+ Addedfast-stream-to-buffer@1.0.0(transitive)
+ Addedfind-up@3.0.0(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedlocate-path@3.0.0(transitive)
+ Addedp-limit@2.3.0(transitive)
+ Addedp-locate@3.0.0(transitive)
+ Addedp-try@2.2.0(transitive)
+ Addedpump@3.0.2(transitive)
+ Addedread-pkg-up@4.0.0(transitive)
+ Addedreadable-stream@3.6.2(transitive)
+ Addedrelative-microtime@2.0.0(transitive)
+ Addedsafe-buffer@5.2.1(transitive)
+ Addedstream-chopper@3.0.1(transitive)
+ Addedstring_decoder@1.3.0(transitive)
+ Addedutil-deprecate@1.0.2(transitive)
- Removednormalize-bool@^1.0.0
- Removeduuid@^3.2.1
- Removedelastic-apm-http-client@5.2.1(transitive)
- Removedfind-up@2.1.0(transitive)
- Removedlocate-path@2.0.0(transitive)
- Removednormalize-bool@1.0.0(transitive)
- Removedp-limit@1.3.0(transitive)
- Removedp-locate@2.0.0(transitive)
- Removedp-try@1.0.0(transitive)
- Removedread-pkg-up@3.0.0(transitive)
- Removeduuid@3.4.0(transitive)
Updatedbasic-auth@^2.0.1
Updatedfast-safe-stringify@^2.0.6
Updatedoriginal-url@^1.2.2
Updatedread-pkg-up@^4.0.0
Updatedsemver@^5.6.0