elastic-apm-node
Version:
The official Elastic APM agent for Node.js
154 lines (120 loc) • 4.39 kB
JavaScript
'use strict'
var util = require('util')
var afterAll = require('after-all-results')
var Value = require('async-value-promise')
var GenericSpan = require('./generic-span')
var parsers = require('../parsers')
var stackman = require('../stackman')
const TEST = process.env.ELASTIC_APM_TEST
module.exports = Span
util.inherits(Span, GenericSpan)
function Span (transaction, name, type, opts) {
if (!opts) {
opts = {
timer: transaction._timer,
childOf: transaction._agent._instrumentation.activeSpan || transaction
}
} else {
opts.timer = transaction._timer
if (!opts.childOf) {
opts.childOf = transaction._agent._instrumentation.activeSpan || transaction
}
}
GenericSpan.call(this, transaction._agent, type, opts)
this._db = null
this._stackObj = null
this.transaction = transaction
this.name = name || 'unnamed'
this._agent._instrumentation.bindingSpan = this
if (this._agent._conf.captureSpanStackTraces) {
this._recordStackTrace()
}
this._agent.logger.debug('start span %o', { span: this.id, parent: this.parentId, trace: this.traceId, name: name, type: type })
}
Span.prototype.customStackTrace = function (stackObj) {
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.end = function (endTime) {
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
}
this._timer.end(endTime)
this._agent._instrumentation._recoverTransaction(this.transaction)
this.ended = true
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)
}
Span.prototype.setDbContext = function (context) {
if (!context) return
this._db = Object.assign(this._db || {}, context)
}
Span.prototype._recordStackTrace = function (obj) {
if (!obj) {
obj = {}
Error.captureStackTrace(obj, Span)
}
var self = this
// NOTE: This uses a promise-like thing and not a *real* promise
// because passing error stacks into a promise context makes it
// uncollectable by the garbage collector.
var stack = new Value()
this._stackObj = stack
// TODO: This is expensive! Consider if there's a way to cache some of this
stackman.callsites(obj, function (err, callsites) {
if (err || !callsites) {
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)
return
}
if (!TEST) callsites = callsites.filter(filterCallsite)
var next = afterAll((err, res) => {
err ? stack.reject(err) : stack.resolve(res)
})
callsites.forEach(function (callsite) {
parsers.parseCallsite(callsite, false, self._agent, next())
})
})
}
Span.prototype._encode = function (cb) {
var self = this
if (!this.ended) return cb(new Error('cannot encode un-ended span'))
if (this._agent._conf.captureSpanStackTraces && this._stackObj) {
this._stackObj.then(
value => done(null, value),
error => done(error)
)
} else {
process.nextTick(done)
}
function done (err, frames) {
if (err) {
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.id,
transaction_id: self.transaction.id,
parent_id: self.parentId,
trace_id: self.traceId,
name: self.name,
type: self.type,
timestamp: self.timestamp,
duration: self.duration(),
context: undefined,
stacktrace: frames,
sync: self.sync
}
if (self._db || self._labels) {
payload.context = {
db: self._db || undefined,
tags: self._labels || undefined
}
}
cb(null, payload)
}
}
function filterCallsite (callsite) {
var filename = callsite.getFileName()
return filename ? filename.indexOf('/node_modules/elastic-apm-node/') === -1 : true
}