Comparing version 1.9.2 to 1.10.0
35
api.js
@@ -8,5 +8,7 @@ 'use strict'; | ||
, recordWeb = require(path.join(__dirname, 'lib', 'metrics', | ||
'recorders', 'http.js')) | ||
, genericRecorder = require(path.join(__dirname, 'lib', 'metrics', 'recorders', | ||
'generic')) | ||
'recorders', 'http.js')) | ||
, recordBackground = require(path.join(__dirname, 'lib', 'metrics', | ||
'recorders', 'other.js')) | ||
, customRecorder = require(path.join(__dirname, 'lib', 'metrics', 'recorders', | ||
'custom')) | ||
; | ||
@@ -403,3 +405,3 @@ | ||
if (txn) { | ||
var segment = tracer.addSegment(name, genericRecorder); | ||
var segment = tracer.addSegment(name, customRecorder); | ||
return tracer.callbackProxy(function () { | ||
@@ -422,4 +424,5 @@ callback.apply(this, arguments); | ||
return tracer.transactionNestProxy(tracer.segmentProxy(function(){ | ||
return tracer.transactionNestProxy('web', tracer.segmentProxy(function(){ | ||
var tx = tracer.getTransaction(); | ||
tx.partialName = NAMES.CUSTOM + NAMES.ACTION_DELIMITER + url; | ||
tx.setName(url, 0); | ||
@@ -432,2 +435,24 @@ tx.webSegment = tracer.addSegment(url, recordWeb); | ||
API.prototype.createBackgroundTransaction = function createBackgroundTransaction(name, group, callback) { | ||
if (callback === undefined && typeof group === 'function') { | ||
callback = group; | ||
group = 'Nodejs'; | ||
} | ||
// FLAG: custom_instrumentation | ||
if (!this.agent.config.feature_flag.custom_instrumentation) { | ||
return callback; | ||
} | ||
var tracer = this.agent.tracer; | ||
return tracer.transactionNestProxy('bg', tracer.segmentProxy(function(){ | ||
var tx = tracer.getTransaction(); | ||
tx.setBackgroundName(name, group); | ||
tx.bgSegment = tracer.addSegment(name, recordBackground); | ||
tx.bgSegment.partialName = group; | ||
callback.apply(this, arguments); | ||
})); | ||
}; | ||
API.prototype.endTransaction = function endTransaction() { | ||
@@ -434,0 +459,0 @@ // FLAG: custom_instrumentation |
@@ -11,3 +11,6 @@ 'use strict'; | ||
var agentVersion = require(path.join(__dirname, 'package.json')).version; | ||
logger.trace("Using New Relic for Node.js version %s.", agentVersion); | ||
try { | ||
@@ -14,0 +17,0 @@ logger.debug("Process was running %s seconds before agent was loaded.", |
@@ -6,3 +6,4 @@ 'use strict'; | ||
proxy: true, | ||
custom_instrumentation: false, | ||
mysql_pool: false, | ||
custom_instrumentation: true, | ||
postgres: false | ||
@@ -9,0 +10,0 @@ }; |
@@ -42,2 +42,6 @@ 'use strict'; | ||
if (agent.config.feature_flag.custom_instrumentation) { | ||
transaction.webSegment = segment; | ||
} | ||
// the error tracer needs a URL for tracing, even though naming overwrites | ||
@@ -44,0 +48,0 @@ transaction.url = request.url; |
@@ -13,10 +13,235 @@ 'use strict'; | ||
function wrapQueriable(queriable, name) { | ||
// may not always be a 'queriable' object, but anything with a .query | ||
// you should pass the appropriate name in for shimmer | ||
shimmer.wrapMethod(queriable, name, 'query', function cb_wrapMethod(query) { | ||
return tracer.segmentProxy(function cb_segmentProxy(/* arguments */) { | ||
// the code below implicitly relies on a transaction, | ||
// so bail early if there is none | ||
// | ||
// we also avoid zero-argument calls | ||
if (!tracer.getTransaction() || arguments.length < 1) { | ||
logger.trace('not tracing because outside a transaction in %s', name); | ||
return query.apply(this, arguments); | ||
} | ||
var sqlString = ''; | ||
var queryVals = []; | ||
// these are used in the onEnd function | ||
var userCallback | ||
, segment | ||
; | ||
// passed to .query | ||
// ends the segment, and then calls the user callback | ||
function onEnd(err) { | ||
logger.trace({error: err}, 'mysql query result in %s', name); | ||
var ret; | ||
if (userCallback) ret = userCallback.apply(null, arguments); | ||
segment.end(); // !m wraithan | ||
return ret; | ||
} | ||
function checkFunc(maybeFunc) { | ||
var out; | ||
if (typeof maybeFunc === 'function') { | ||
out = tracer.callbackProxy(maybeFunc); | ||
} | ||
return out; | ||
} | ||
// This is just a massive argument hunt | ||
// because you can call .query in many ways. | ||
// | ||
// You should populate `userCallback` after this block with a callback. | ||
// Optionally you may populate `queryVals` and `sqlString`. | ||
// The value in `sqlString` will show up in the UI | ||
var vargs = []; | ||
if (arguments.length === 1 && typeof arguments[0] === 'object') { | ||
// .query(query) | ||
// query query is a Query object and contains ._callback and .sql | ||
userCallback = checkFunc(arguments[0]._callback); | ||
arguments[0]._callback = onEnd; | ||
vargs.push(arguments[0]); | ||
} else if (arguments.length === 1) { | ||
// either .query(callback) or .query(sql) | ||
// in the latter case we append our own callback for instrumentation | ||
if (!(userCallback = checkFunc(arguments[0]))) { | ||
vargs.push(arguments[0]); | ||
} | ||
vargs.push(onEnd); | ||
} else if (arguments.length === 2) { | ||
// .query(sql, callback) or .query(sql, values) | ||
// in the latter case we append our own callback for instrumentation | ||
vargs.push(sqlString = arguments[0]); | ||
if (!(userCallback = checkFunc(arguments[1]))) { | ||
vargs.push(arguments[1]); | ||
} | ||
vargs.push(onEnd); | ||
} else { | ||
// .query(sql, values, callback) or unknown | ||
// in the latter case, we just omit measuring | ||
vargs.push(sqlString = arguments[0]); | ||
vargs.push(queryVals = arguments[1]); | ||
if (!(userCallback = checkFunc(arguments[2]))) { | ||
vargs.push(arguments[2]); | ||
} else { | ||
vargs.push(onEnd); | ||
} | ||
} | ||
logger.trace({query: sqlString}, 'wrapping query arguments in %s', name); | ||
// name the metric | ||
var ps = parseSql(MYSQL.PREFIX, sqlString); | ||
var segmentName = MYSQL.STATEMENT + ps.model + '/' + ps.operation; | ||
logger.trace({sql: sqlString, parsed: ps}, 'capturing sql in %s', name); | ||
// we will end the segment in onEnd above | ||
segment = tracer.addSegment(segmentName, ps.recordMetrics.bind(ps)); | ||
if (this.config && this.config.connectionConfig) { | ||
segment.port = this.config.port || this.config.connectionConfig.port; | ||
segment.host = this.config.host || this.config.connectionConfig.host; | ||
} | ||
return query.apply(queriable, vargs); | ||
}); | ||
}); | ||
} | ||
function getVargs(args) { | ||
var callback; | ||
var vargs = []; | ||
if (args.length === 1) { | ||
callback = args[0]; | ||
} else if (args.length === 2) { | ||
vargs.push(args[0]); | ||
callback = args[1]; | ||
} else { | ||
vargs.push(args[0]); | ||
vargs.push(args[1]); | ||
callback = args[2]; | ||
} | ||
logger.trace({args: args, vargs: vargs}, 'parsed getConnection arguments'); | ||
return { | ||
vargs : vargs, | ||
callback : callback, | ||
}; | ||
} | ||
function getConnectionHandler(dbObject, getConnectionMethod) { | ||
return function wrap_getConnection() { // getConnection | ||
var args = getVargs(arguments); | ||
var getConnectionCallback; | ||
// let's verify that we actually have a callback, | ||
// otherwise we should just pass on wrapping it | ||
// | ||
// TODO: test case where no callback is supplied | ||
var isCallback = args.callback && typeof args.callback === 'function'; | ||
// The mysql module has internal retry logic that will call | ||
// getConnection again with our wrapped callback. | ||
// We should avoid re-wrapping the callback when possible, | ||
// although nothing bad happens when we fail this, it just | ||
// makes stack traces a little better in errors. | ||
if (!isCallback || !args.callback.__NR_original_callback) { | ||
var proxiedCallback = tracer.callbackProxy(args.callback); | ||
getConnectionCallback = function getConnectionCallback(err, connection) { | ||
// we need to patch the connection objects .query method | ||
wrapQueriable(connection, 'connection'); | ||
proxiedCallback(err, connection); | ||
}; | ||
// tag so we can avoid re-wrapping | ||
getConnectionCallback.__NR_original_callback = args.callback; | ||
} else { | ||
// the connection is already wrapped | ||
logger.trace('getConnection callback already wrapped'); | ||
getConnectionCallback = args.callback; | ||
} | ||
args.vargs.push(getConnectionCallback); | ||
return getConnectionMethod.apply(dbObject, args.vargs); | ||
}; | ||
} | ||
// FIXME: need a more general way of differentiating between driver versions | ||
if (mysql && mysql.createConnection) { | ||
// congratulations, you have node-mysql 2.0 | ||
shimmer.wrapMethod(mysql, 'mysql', 'createConnection', function cb_wrapMethod(createConnection) { | ||
// FLAG: mysql_pool | ||
if (agent.config && | ||
agent.config.feature_flag && | ||
agent.config.feature_flag.mysql_pool) { | ||
shimmer.wrapMethod(mysql, 'mysql.prototype', 'createPoolCluster', | ||
function cb_wrapMethod(createPoolCluster) { | ||
// this is generally called outside of a transaction, | ||
// so we don't need/care about preserving | ||
// the continuation, but we do need to patch the returned object | ||
return function not_in_transaction() { | ||
var poolCluster = createPoolCluster.apply(mysql, arguments); | ||
shimmer.wrapMethod(poolCluster, 'poolCluster', 'of', | ||
function cb_wrapMethod(of) { | ||
return function () { | ||
var ofCluster = of.apply(poolCluster, arguments); | ||
shimmer.wrapMethod(ofCluster, 'poolCluster', 'getConnection', | ||
function cb_wrapMethod(getConnection) { | ||
return getConnectionHandler(ofCluster, getConnection); | ||
}); | ||
return ofCluster; | ||
}; | ||
}); | ||
shimmer.wrapMethod(poolCluster, 'poolCluster', 'getConnection', | ||
function cb_wrapMethod(getConnection) { | ||
return getConnectionHandler(poolCluster, getConnection); | ||
}); | ||
return poolCluster; | ||
}; | ||
}); | ||
shimmer.wrapMethod(mysql, 'mysql', 'createPool', | ||
function cb_wrapMethod(createPool) { | ||
return function cb_segmentProxy() { | ||
var pool = createPool.apply(mysql, arguments); | ||
shimmer.wrapMethod(pool, 'pool', 'getConnection', | ||
function cb_wrapMethod(getConnection) { | ||
return getConnectionHandler(pool, getConnection); | ||
}); | ||
// patch the pools .query method | ||
wrapQueriable(pool, 'pool'); | ||
return pool; | ||
}; | ||
}); | ||
} // FLAG: mysql_pool | ||
shimmer.wrapMethod(mysql, 'mysql', 'createConnection', | ||
function cb_wrapMethod(createConnection) { | ||
return tracer.segmentProxy(function cb_segmentProxy() { | ||
var connection = createConnection.apply(this, arguments); | ||
shimmer.wrapMethod(connection, 'connection', 'query', function cb_wrapMethod(query) { | ||
shimmer.wrapMethod(connection, 'connection', 'query', | ||
function cb_wrapMethod(query) { | ||
return tracer.segmentProxy(function cb_segmentProxy(sql, values, callback) { | ||
@@ -23,0 +248,0 @@ |
@@ -76,2 +76,2 @@ 'use strict'; | ||
}); | ||
}; | ||
}; |
@@ -93,2 +93,3 @@ 'use strict'; | ||
WEB : 'WebTransaction', | ||
BACKGROUND : 'OtherTransaction', | ||
HTTP : 'HttpDispatcher', | ||
@@ -95,0 +96,0 @@ CONTROLLER : 'Controller', |
@@ -25,3 +25,2 @@ 'use strict'; | ||
'pg', | ||
'pg.js', | ||
'redis', | ||
@@ -56,8 +55,14 @@ 'restify' ]; | ||
function _postLoad(agent, nodule, name) { | ||
var instrumentation; | ||
var base = path.basename(name); | ||
// to allow for instrumenting both 'pg' and 'pg.js'. | ||
if (name === 'pg.js') instrumentation = 'pg'; | ||
else instrumentation = base; | ||
// necessary to prevent instrument() from causing an infinite loop | ||
if (INSTRUMENTATION.indexOf(base) !== -1) { | ||
if (INSTRUMENTATION.indexOf(instrumentation) !== -1) { | ||
logger.trace('Instrumenting %s.', base); | ||
var filename = path.join(__dirname, 'instrumentation', base + '.js'); | ||
var filename = path.join(__dirname, 'instrumentation', instrumentation + '.js'); | ||
instrument(agent, base, filename, nodule); | ||
@@ -64,0 +69,0 @@ } |
@@ -50,2 +50,3 @@ 'use strict'; | ||
this.webSegment = null; | ||
this.bgSegment = null; | ||
} | ||
@@ -144,2 +145,13 @@ | ||
Transaction.prototype.setBackgroundName = function setBackgroundName(name, group) { | ||
var fullName = NAMES.BACKGROUND + NAMES.ACTION_DELIMITER + group + NAMES.ACTION_DELIMITER + name; | ||
var normalizer = this.agent.transactionNameNormalizer; | ||
if (normalizer.isIgnored(fullName)) this.ignore = true; | ||
this.name = normalizer.normalize(fullName); | ||
if (this.forceIgnore === true || this.forceIgnore === false) { | ||
this.ignore = this.forceIgnore; | ||
} | ||
}; | ||
/** | ||
@@ -146,0 +158,0 @@ * Measure the duration of an operation named by a metric, optionally |
@@ -172,3 +172,7 @@ 'use strict'; | ||
*/ | ||
Tracer.prototype.transactionNestProxy = function transactionNestProxy(handler) { | ||
Tracer.prototype.transactionNestProxy = function transactionNestProxy(type, handler) { | ||
if (handler === undefined && typeof type === 'function') { | ||
handler = type; | ||
type = undefined; | ||
} | ||
// if there's no handler, there's nothing to proxy. | ||
@@ -185,9 +189,15 @@ if (!handler) return; | ||
} | ||
var transaction = self.getTransaction(); | ||
var transaction = self.getTransaction() || new Transaction(self.agent) | ||
, proxied = this | ||
, args = self.slice(arguments) | ||
; | ||
if (transaction) { | ||
if ((type === 'web' && transaction.bgSegment) || (type === 'bg' && transaction.webSegment)) { | ||
transaction = new Transaction(self.agent); | ||
} | ||
} else { | ||
transaction = new Transaction(self.agent); | ||
} | ||
var proxied = this; | ||
var args = self.slice(arguments); | ||
var returned; | ||
var returned; | ||
namespace.bind(function cb_bind() { | ||
@@ -210,2 +220,29 @@ self.setTransaction(transaction); | ||
Tracer.prototype.transactionForceNewProxy = function transactionNestProxy(type, handler) { | ||
// if there's no handler, there's nothing to proxy. | ||
if (!handler) return; | ||
var self = this; | ||
var wrapped = function wrapTransactionInvocation() { | ||
var transaction = new Transaction(self.agent) | ||
, proxied = this | ||
, args = self.slice(arguments) | ||
; | ||
var returned; | ||
namespace.bind(function cb_bind() { | ||
self.setTransaction(transaction); | ||
self.setSegment(transaction.getTrace().root); | ||
returned = namespace.bind(handler).apply(proxied, args); | ||
}, Object.create(null))(); | ||
return returned; | ||
}; | ||
wrapped[ORIGINAL] = handler; | ||
return wrapped; | ||
}; | ||
/** | ||
@@ -212,0 +249,0 @@ * Use segmentProxy to wrap a closure that is a top-level handler that is |
@@ -11,3 +11,3 @@ /** | ||
*/ | ||
app_name : ['éxample'], | ||
app_name : ['My Application'], | ||
/** | ||
@@ -14,0 +14,0 @@ * Your New Relic license key. |
10
NEWS.md
@@ -0,1 +1,11 @@ | ||
### v1.10.0 (2014-08-15): | ||
* Custom instrumentation | ||
The agent now supports the ability to annotate application code to provide | ||
customized instrumentation. This includes the ability to time both web and | ||
background transactions, and add tracers to measure activity within | ||
transactions like querying a database. Documentation available at | ||
https://docs.newrelic.com/docs/agents/nodejs-agent/supported-features/nodejs-custom-instrumentation | ||
### v1.9.2 (2014-08-08): | ||
@@ -2,0 +12,0 @@ |
{ | ||
"name": "newrelic", | ||
"version": "1.9.2", | ||
"version": "1.10.0", | ||
"author": "New Relic Node.js agent team <nodejs@newrelic.com>", | ||
@@ -5,0 +5,0 @@ "licenses": [ |
@@ -379,2 +379,61 @@ [](https://nodei.co/npm/newrelic/) | ||
### Custom Instrumentation | ||
Custom transaction should be used for instrumenting `socket.io` or other | ||
varieties of socket servers, and background jobs. These are things that the | ||
agent can't automatically instrument because without your knowledge of your | ||
application, the agent can't tell when they should begin and end. | ||
Read more at: | ||
https://docs.newrelic.com/docs/agents/nodejs-agent/supported-features/nodejs-custom-instrumentation | ||
#### newrelic.createWebTransaction(url, handle) | ||
`url` is the name of the web transaction. It should be pretty static, not | ||
including anything like user ids or any other data that is very specific to the | ||
request. `handle` is the function you'd like to wrap in the web transaction. | ||
Both custom and auto instrumentation will be captured as part of the | ||
transaction. | ||
If called within an active web transaction, it will act as a nested tracer. If | ||
called within an active background transaction, it will create a new, | ||
independent transaction and any calls within the `handle` will be bound to the | ||
new web transaction. | ||
Custom transactions **must** be ended manually by calling `endTransaction()`. | ||
Timing for custom transaction starts from when the returned wrapped function is | ||
called until `endTransaction()` is called. | ||
#### newrelic.createBackgroundTransaction(name, [group], handle) | ||
`name` is the name of the job. It should be pretty static, and not include job | ||
ids or anything very specific to that run of the job. `group` is optional, and | ||
allows you to group types of jobs together. This should follow similar rules as | ||
the `name`. `handle` is a function that encompases your background job. Both | ||
custom and auto instrumentation will be captured as part of the transaction. | ||
If called within an active background transaction, it will act as a nested | ||
tracer. If called within an active web transaction, it will create a new | ||
transaction and any calls within the `handle` will be bound to the new, | ||
independent background transaction. | ||
Custom transactions **must** be ended manually by calling `endTransaction()`. | ||
Timing for custom transaction starts from when the returned wrapped function is | ||
called until `endTransaction()` is called. | ||
#### newrelic.endTransaction() | ||
This takes no arguments and must be called to end any custom transaction. It | ||
will detect what kind of transaction was active and end it. | ||
#### newrelic.createTracer(name, handle) | ||
`name` is the name of the tracer. It will show up as a segment in your | ||
transaction traces and create its own metric. `handle` is the function to be | ||
bound to the tracer. | ||
Timing is from when `createTracer` is called until the `handle` done executing. | ||
This should be called inside of a transaction to get data. If it is called | ||
outside of a transaction it will just pass through. | ||
### The fine print | ||
@@ -381,0 +440,0 @@ |
@@ -36,3 +36,3 @@ 'use strict'; | ||
// Normally createTracer returns the a wrapped callback, instead we should just | ||
// Normally the follow 3 calls return a wrapped callback, instead we should just | ||
// return the callback in its unwrapped state. | ||
@@ -44,3 +44,3 @@ Stub.prototype.createTracer = function(name, callback) { | ||
Stub.prototype.createWebTransaction = function(name, callback) { | ||
Stub.prototype.createWebTransaction = function(url, callback) { | ||
logger.debug('Not calling createWebTransaction because New Relic is disabled.'); | ||
@@ -50,2 +50,7 @@ return callback; | ||
Stub.prototype.createBackgroundTransaction = function(name, callback) { | ||
logger.debug('Not calling createBackgroundTransaction because New Relic is disabled.'); | ||
return callback; | ||
}; | ||
module.exports = Stub; |
@@ -116,2 +116,16 @@ 'use strict'; | ||
}); | ||
it('should record a metric for the custom segment', function (done) { | ||
agent.on('transactionFinished', function (transaction) { | ||
expect(transaction.metrics.unscoped).to.have.property('Custom/custom:segment'); | ||
done(); | ||
}); | ||
helper.runInTransaction(agent, function (transaction) { | ||
var markedFunction = api.createTracer('custom:segment', function () { | ||
transaction.end(); | ||
}); | ||
markedFunction(); | ||
}); | ||
}); | ||
}); | ||
@@ -135,3 +149,3 @@ | ||
}); | ||
expect(agent.tracer.getTransaction()).to.not.exist; | ||
txHandler(); | ||
@@ -207,10 +221,216 @@ }); | ||
it('endTransaction should not throw an exception if there is no transaction active', function () { | ||
var tx = agent.tracer.getTransaction(); | ||
expect(tx).to.not.exist; | ||
expect(function () { | ||
it('should create proper metrics', function (done) { | ||
var txHandler = api.createWebTransaction('/custom/transaction', function () { | ||
var tx = agent.tracer.getTransaction(); | ||
expect(tx).to.exist; | ||
var expectedMetrics = [ | ||
{"name": "WebTransaction"}, | ||
{"name": "HttpDispatcher"}, | ||
{"name": "WebTransaction/Custom//custom/transaction"}, | ||
{"name": "Apdex/null"}, | ||
{"name": "Apdex"}, | ||
{"name": "WebTransaction/Custom//custom/transaction", "scope": "WebTransaction/Custom//custom/transaction"} | ||
]; | ||
tx.end(); | ||
tx.metrics.toJSON().forEach(function (element, index) { | ||
expect(element[0]).to.be.deep.equal(expectedMetrics[index]); | ||
}); | ||
done(); | ||
}); | ||
expect(agent.tracer.getTransaction()).to.not.exist; | ||
txHandler(); | ||
}); | ||
it('it should create a new transaction when nested within a background transaction', function (done) { | ||
var bgHandler = api.createBackgroundTransaction('background:job', function () { | ||
var tx = agent.tracer.getTransaction(); | ||
expect(tx).to.exist; | ||
expect(tx.name).to.be.equal('OtherTransaction/Nodejs/background:job'); | ||
expect(tx.webSegment).to.not.exist; | ||
expect(tx.bgSegment).to.exist; | ||
var webHandler = api.createWebTransaction('/custom/transaction', function () { | ||
var tx = agent.tracer.getTransaction(); | ||
expect(tx).to.exist; | ||
expect(tx.url).to.be.equal('/custom/transaction'); | ||
expect(tx.webSegment).to.exist; | ||
expect(tx.bgSegment).to.not.exist; | ||
// clean up tx so it doesn't cause other problems | ||
tx.end(); | ||
done(); | ||
}); | ||
webHandler(); | ||
// clean up tx so it doesn't cause other problems | ||
tx.end(); | ||
}); | ||
expect(agent.tracer.getTransaction()).to.not.exist; | ||
bgHandler(); | ||
}); | ||
}); | ||
describe('when creating an background transaction', function () { | ||
it('should return a function', function () { | ||
var txHandler = api.createBackgroundTransaction('background:job', function () {}); | ||
expect(txHandler).to.be.a('function'); | ||
}); | ||
it('should create a transaction', function (done) { | ||
var txHandler = api.createBackgroundTransaction('background:job', function () { | ||
var tx = agent.tracer.getTransaction(); | ||
expect(tx).to.exist; | ||
expect(tx.name).to.be.equal('OtherTransaction/Nodejs/background:job'); | ||
// clean up tx so it doesn't cause other problems | ||
tx.end(); | ||
done(); | ||
}); | ||
expect(agent.tracer.getTransaction()).to.not.exist; | ||
txHandler(); | ||
}); | ||
it('should create an outermost segment', function (done) { | ||
var txHandler = api.createBackgroundTransaction('background:job', function () { | ||
var tx = agent.tracer.getTransaction(); | ||
expect(tx).to.exist; | ||
var trace = tx.getTrace(); | ||
expect(trace.root.children).to.have.length(1); | ||
// clean up tx so it doesn't cause other problems | ||
tx.end(); | ||
done(); | ||
}); | ||
txHandler(); | ||
}); | ||
it('should respect the in play transaction and not create a new one', function (done) { | ||
var txHandler = api.createBackgroundTransaction('background:job', function (outerTx) { | ||
var tx = agent.tracer.getTransaction(); | ||
expect(tx).to.be.equal(outerTx); | ||
var trace = tx.getTrace(); | ||
expect(trace.root.children).to.have.length(1); | ||
done(); | ||
}); | ||
helper.runInTransaction(agent, function (transaction) { | ||
txHandler(transaction); | ||
transaction.end(); | ||
}); | ||
}); | ||
it('should nest its segment within an in play segment', function (done) { | ||
var txHandler = api.createBackgroundTransaction('background:job', function (outerTx) { | ||
var tx = agent.tracer.getTransaction(); | ||
expect(tx).to.be.equal(outerTx); | ||
var trace = tx.getTrace(); | ||
expect(trace.root.children).to.have.length(1); | ||
var child = trace.root.children[0]; | ||
expect(child.name).to.equal('outer'); | ||
expect(child.children).to.have.length(1); | ||
done(); | ||
}); | ||
helper.runInTransaction(agent, function (transaction) { | ||
agent.tracer.addSegment('outer'); | ||
txHandler(transaction); | ||
transaction.end(); | ||
}); | ||
}); | ||
it('should be ended by calling endTransaction', function (done) { | ||
var txHandler = api.createWebTransaction('background:job', function () { | ||
var tx = agent.tracer.getTransaction(); | ||
expect(tx.isActive()).to.be.true; | ||
api.endTransaction(); | ||
}).to.not.throw; | ||
expect(tx.isActive()).to.be.false; | ||
done(); | ||
}); | ||
txHandler(); | ||
}); | ||
it('should create proper metrics with default group name', function (done) { | ||
var txHandler = api.createBackgroundTransaction('background:job', function () { | ||
var tx = agent.tracer.getTransaction(); | ||
expect(tx).to.exist; | ||
var expectedMetrics = [ | ||
{"name": "OtherTransaction/Nodejs/background:job"}, | ||
{"name": "OtherTransaction/all"}, | ||
{"name": "OtherTransaction/Nodejs/all"}, | ||
{"name": "OtherTransaction/Nodejs/background:job", "scope": "OtherTransaction/Nodejs/background:job"} | ||
]; | ||
tx.end(); | ||
tx.metrics.toJSON().forEach(function (element, index) { | ||
expect(element[0]).to.be.deep.equal(expectedMetrics[index]); | ||
}); | ||
done(); | ||
}); | ||
expect(agent.tracer.getTransaction()).to.not.exist; | ||
txHandler(); | ||
}); | ||
it('should create proper metrics with group name', function (done) { | ||
var txHandler = api.createBackgroundTransaction('background:job', 'thinger', function () { | ||
var tx = agent.tracer.getTransaction(); | ||
expect(tx).to.exist; | ||
var expectedMetrics = [ | ||
{"name": "OtherTransaction/thinger/background:job"}, | ||
{"name": "OtherTransaction/all"}, | ||
{"name": "OtherTransaction/thinger/all"}, | ||
{"name": "OtherTransaction/thinger/background:job", "scope": "OtherTransaction/thinger/background:job"} | ||
]; | ||
tx.end(); | ||
tx.metrics.toJSON().forEach(function (element, index) { | ||
expect(element[0]).to.be.deep.equal(expectedMetrics[index]); | ||
}); | ||
done(); | ||
}); | ||
expect(agent.tracer.getTransaction()).to.not.exist; | ||
txHandler(); | ||
}); | ||
it('it should create a new transaction when nested within a background transaction', function (done) { | ||
var webHandler = api.createWebTransaction('/custom/transaction', function () { | ||
var tx = agent.tracer.getTransaction(); | ||
expect(tx).to.exist; | ||
expect(tx.url).to.be.equal('/custom/transaction'); | ||
expect(tx.webSegment).to.exist; | ||
expect(tx.bgSegment).to.not.exist; | ||
var bgHandler = api.createBackgroundTransaction('background:job', function () { | ||
var tx = agent.tracer.getTransaction(); | ||
expect(tx).to.exist; | ||
expect(tx.name).to.be.equal('OtherTransaction/Nodejs/background:job'); | ||
expect(tx.webSegment).to.not.exist; | ||
expect(tx.bgSegment).to.exist; | ||
// clean up tx so it doesn't cause other problems | ||
tx.end(); | ||
done(); | ||
}); | ||
bgHandler(); | ||
// clean up tx so it doesn't cause other problems | ||
tx.end(); | ||
}); | ||
expect(agent.tracer.getTransaction()).to.not.exist; | ||
webHandler(); | ||
}); | ||
}); | ||
it('endTransaction should not throw an exception if there is no transaction active', function () { | ||
var tx = agent.tracer.getTransaction(); | ||
expect(tx).to.not.exist; | ||
expect(function () { | ||
api.endTransaction(); | ||
}).to.not.throw; | ||
}); | ||
}); |
@@ -18,3 +18,3 @@ 'use strict'; | ||
it("should export 11 API calls", function () { | ||
expect(Object.keys(api.constructor.prototype).length).equal(11); | ||
expect(Object.keys(api.constructor.prototype).length).equal(12); | ||
}); | ||
@@ -113,2 +113,14 @@ | ||
it("shouldn't throw when a custom background transaction is added", function () { | ||
expect(function () { | ||
api.createBackgroundTransaction('name', function nop(){}); | ||
}).not.throws(); | ||
}); | ||
it("should return a function when calling createBackgroundTransaction", function () { | ||
function myNop () {} | ||
var retVal = api.createBackgroundTransaction('name', myNop); | ||
expect(retVal).to.be.equal(myNop); | ||
}); | ||
it("shouldn't throw when a transaction is ended", function () { | ||
@@ -115,0 +127,0 @@ expect(function () { |
@@ -16,2 +16,3 @@ var flags = require('../lib/feature_flags.js'); | ||
'custom_instrumentation', | ||
'mysql_pool', | ||
'postgres' | ||
@@ -18,0 +19,0 @@ ]; |
@@ -115,3 +115,7 @@ 'use strict'; | ||
*/ | ||
runInTransaction : function runInTransaction(agent, callback) { | ||
runInTransaction : function runInTransaction(agent, type, callback) { | ||
if (callback === undefined && typeof type === 'function') { | ||
callback = type; | ||
type = undefined; | ||
} | ||
if (!(agent && callback)) { | ||
@@ -118,0 +122,0 @@ throw new TypeError("Must include both agent and function!"); |
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
2025133
487
44735
614
510