signalfx-tracing
Version:
Provides auto-instrumentation for JavaScript libraries and frameworks
136 lines (123 loc) • 4.16 kB
JavaScript
const spanSymbol = '_sfxSpan'
const maxQueryLength = 1024
function createWrapBuilder (tracer, config) {
return function wrapQueryBuilder (original) {
return function queryBuilderWithTrace () {
const scope = tracer.scope()
const span = scope.active()
const builder = original.apply(this, arguments)
return Object.defineProperty(builder, spanSymbol, { value: span })
}
}
}
function createWrapRunner (wrapper, tracer, config) {
return function wrapRunner (original) {
return function runnerWithTrace () {
const runner = original.apply(this, arguments)
let formatter
if (runner.client && runner.client._formatQuery) {
formatter = runner.client._formatQuery.bind(runner.client)
} else if (runner.client.SqlString) {
formatter = runner.client.SqlString.format.bind(runner.client.SqlString)
}
wrapper.wrap(runner, 'query', createWrapRunnerQuery(tracer, config, formatter))
return runner
}
}
}
function createWrapRunnerQuery (tracer, config, formatter) {
return function wrapQuery (original) {
return function queryWithTrace (q) {
const scope = tracer.scope()
const childOf = this.builder[spanSymbol]
const tags = {
'component': 'knex',
'db.statement': q.sql.substr(0, maxQueryLength)
}
if (q.timeout !== undefined) {
tags.timeout = q.timeout
}
let spanName = 'knex.client.runner'
if (q.method !== undefined) {
spanName = `knex.client.runner.${q.method}`
}
const span = tracer.startSpan(spanName, {
childOf,
tags
})
setDBTags(this, span)
return scope.activate(span, () => {
return new Promise((resolve, reject) => {
// we can't use then.catch.finally because finally is not supported
// on node 8 which knex still supports.
const that = this
const query = arguments[0]
let formattedQuery = ''
if (formatter && query && query.sql) {
formattedQuery = formatter(query.sql, query.bindings || [])
}
original.apply(this, arguments)
.then(function () {
resolve.apply(that, arguments)
span.finish()
})
.catch(function (e) {
addError(span, e, formattedQuery)
reject.apply(that, arguments)
span.finish()
})
})
})
}
}
}
function addError (span, error, formattedQuery) {
span.addTags({
'error': true,
'sfx.error.kind': error.name,
'sfx.error.message': error.message.replace(formattedQuery + ' - ', ''),
'sfx.error.stack': error.stack
})
return error
}
function setDBTags (obj, span) {
if (obj.client && obj.client.config) {
const config = obj.client.config
if (config.client) {
span.setTag('db.type', config.client)
}
if (config.connection) {
if (config.connection.user) {
span.setTag('db.user', config.connection.user)
}
const instance = config.connection.filename || config.connection.database
if (instance) {
span.setTag('db.instance', instance)
}
}
}
}
function patchKnex (version, basePath) {
return [
{
name: 'knex',
versions: version,
file: `${basePath}/client.js`,
patch (Client, tracer, config) {
this.wrap(Client.prototype, 'queryBuilder', createWrapBuilder(tracer, config))
this.wrap(Client.prototype, 'schemaBuilder', createWrapBuilder(tracer, config))
this.wrap(Client.prototype, 'raw', createWrapBuilder(tracer, config))
this.wrap(Client.prototype, 'runner', createWrapRunner(this, tracer, config))
},
unpatch (Client) {
this.unwrap(Client.prototype, 'runner')
this.unwrap(Client.prototype, 'raw')
this.unwrap(Client.prototype, 'schemaBuilder')
this.unwrap(Client.prototype, 'queryBuilder')
}
}
]
}
module.exports = patchKnex(['>=0.10.0 <0.18.0', '>=0.19.0 <=0.20.10', '>=0.20.11 <0.21.0'], 'lib')
.concat(patchKnex(['>=0.18.0 <0.19.0'], 'src'))