Comparing version 1.1.1 to 1.2.0
@@ -134,4 +134,3 @@ 'use strict'; | ||
sampler.start(this); | ||
this.harvesterHandle = setInterval(this.harvest.bind(this), | ||
this.config.data_report_period * TO_MILLIS); | ||
this.restartHarvester(this.config.data_report_period); | ||
@@ -149,7 +148,13 @@ this.emit('ready'); | ||
Agent.prototype.stop = function () { | ||
if (this.harvesterHandle) clearInterval(this.harvesterHandle); | ||
if (this.harvesterHandle) { | ||
clearInterval(this.harvesterHandle); | ||
this.harvesterHandle = null; | ||
} | ||
sampler.stop(); | ||
if (this.connection) { | ||
if (this.connectionHandle) clearTimeout(this.connectionHandle); | ||
if (this.connectionHandle) { | ||
clearTimeout(this.connectionHandle); | ||
this.connectionHandle = null; | ||
} | ||
this.connection.end(); | ||
@@ -192,9 +197,21 @@ this.connectionFailures = null; | ||
if (this.harvesterHandle) { | ||
clearInterval(this.harvesterHandle); | ||
// force a harvest now, to be safe | ||
this.harvest(); | ||
this.harvesterHandle = setInterval(this.harvest.bind(this), interval * TO_MILLIS); | ||
this.restartHarvester(interval); | ||
} | ||
}; | ||
/** | ||
* Safely (re)start the harvest timer, and ensure that the harvest cycle won't | ||
* keep an application from exiting if nothing else is happening to keep it up. | ||
* | ||
* @param {number} harvestSeconds How many seconds between harvests. | ||
*/ | ||
Agent.prototype.restartHarvester = function (harvestSeconds) { | ||
if (this.harvesterHandle) clearInterval(this.harvesterHandle); | ||
this.harvesterHandle = setInterval(this.harvest.bind(this), harvestSeconds * TO_MILLIS); | ||
// timer.unref is 0.9+ | ||
if (this.harvesterHandle.unref) this.harvesterHandle.unref(); | ||
}; | ||
Agent.prototype.onNewMappings = function (rules) { | ||
@@ -258,2 +275,4 @@ this.mapper.load(rules); | ||
backoff.interval * TO_MILLIS); | ||
// timer.unref is 0.9+ | ||
if (this.connectionHandle.unref) this.connectionHandle.unref(); | ||
}; | ||
@@ -426,10 +445,18 @@ | ||
Agent.prototype.mergeMetrics = function (data, error) { | ||
if (this.connection && this.connection.discard(error.statusCode)) { | ||
logger.warn("Got %s from collector, discarding metrics.", error.statusCode); | ||
return; | ||
if (error) { | ||
if (this.connection && this.connection.discard(error.statusCode)) { | ||
logger.warn("Got %s from collector, discarding metrics.", error.statusCode); | ||
return; | ||
} | ||
} | ||
logger.warn(error, "Merging metrics from last harvest cycle because:"); | ||
if (error && error.message) { | ||
logger.warn("Merging metrics from last harvest cycle due to delivery error: %s", | ||
error.message); | ||
} | ||
else { | ||
logger.warn("Merging metrics from last harvest cycle due to delivery error."); | ||
} | ||
this.metrics.merge(data[3]); | ||
if (data) this.metrics.merge(data[3]); | ||
}; | ||
@@ -449,11 +476,18 @@ | ||
Agent.prototype.mergeErrors = function (data, error) { | ||
if (this.connection && this.connection.discard(error.statusCode)) { | ||
logger.warn("Got %s from collector, discarding traced errors.", error.statusCode); | ||
return; | ||
if (error) { | ||
if (this.connection && this.connection.discard(error.statusCode)) { | ||
logger.warn("Got %s from collector, discarding traced errors.", error.statusCode); | ||
return; | ||
} | ||
} | ||
logger.warn(error, "Merging errors from last harvest cycle because:"); | ||
if (error && error.message) { | ||
logger.warn("Merging error traces from last harvest cycle due to delivery error: %s", | ||
error.message); | ||
} | ||
else { | ||
logger.warn("Merging error traces from last harvest cycle due to delivery error."); | ||
} | ||
this.errors.merge(data[1]); | ||
this.emit('errorsMerged'); | ||
if (data) this.errors.merge(data[1]); | ||
}; | ||
@@ -460,0 +494,0 @@ |
@@ -18,5 +18,5 @@ 'use strict'; | ||
var DEFAULT_FILENAME = 'newrelic.js'; | ||
var AZURE_APP_NAME = 'APP_POOL_ID'; | ||
var CONFIG_FILE_LOCATIONS = [ | ||
process.cwd(), | ||
path.dirname(process.mainModule.filename), | ||
process.env.NEW_RELIC_HOME, | ||
@@ -26,4 +26,8 @@ process.env.HOME, | ||
]; | ||
var AZURE_APP_NAME = 'APP_POOL_ID'; | ||
// the REPL has no main module | ||
if (process.mainModule && process.mainModule.filename) { | ||
CONFIG_FILE_LOCATIONS.splice(1, 0, path.dirname(process.mainModule.filename)); | ||
} | ||
/* | ||
@@ -30,0 +34,0 @@ * ENV_MAPPING, LIST_VARS, and BOOLEAN_VARS could probably be unified and |
@@ -10,2 +10,15 @@ 'use strict'; | ||
/** | ||
* true if and only if path exists and is a directory | ||
* should now throw | ||
*/ | ||
function existsDir(dirPath) { | ||
if (!exists(dirPath)) return false; | ||
var stat = fs.statSync(dirPath); | ||
if (stat) return stat.isDirectory(); | ||
return false; | ||
} | ||
var remapping = { | ||
@@ -64,3 +77,3 @@ node_install_npm : "npm installed?", | ||
var packages = []; | ||
if (exists(root)) { | ||
if (existsDir(root)) { | ||
packages = fs.readdirSync(root) | ||
@@ -90,3 +103,3 @@ .filter(function (entry) { | ||
var children = []; | ||
if (exists(root)) { | ||
if (existsDir(root)) { | ||
fs.readdirSync(root) | ||
@@ -93,0 +106,0 @@ .filter(function (entry) { |
@@ -111,15 +111,13 @@ 'use strict'; | ||
var errored = false; | ||
if (transaction.exceptions.length > 0) { | ||
errored = true; | ||
transaction.exceptions.forEach(function (exception) { | ||
this.add(transaction, exception); | ||
}, this); | ||
} | ||
else if (urltils.isError(this.config, transaction.statusCode)) { | ||
errored = true; | ||
this.add(transaction); | ||
} | ||
if (urltils.isError(this.config, transaction.statusCode) || | ||
(transaction.statusCode < 1 && transaction.exceptions.length > 0)) { | ||
if (transaction.exceptions.length > 0) { | ||
transaction.exceptions.forEach(function (exception) { | ||
this.add(transaction, exception); | ||
}, this); | ||
} | ||
else { | ||
this.add(transaction); | ||
} | ||
if (errored) { | ||
var count = metrics.getOrCreateMetric(NAMES.ERRORS.PREFIX + transaction.name); | ||
@@ -175,25 +173,2 @@ count.incrementCallCount(1); | ||
/** | ||
* In an effort to trap more synchronous exceptions before instrumented | ||
* frameworks get their claws into them, this function runs a wrapped | ||
* function, collecting any exceptions that are thrown, and then rethrowing | ||
* the exception in an effort to not change how user apps see errors. | ||
* | ||
* @param Function monitored The function to be called. | ||
* @param Transaction transaction The NR context for the call. | ||
*/ | ||
ErrorTracer.prototype.monitor = function (monitored, transaction) { | ||
if (typeof monitored !== 'function') { | ||
throw new Error("First parameter of monitor must be a function!"); | ||
} | ||
try { | ||
return monitored(); | ||
} | ||
catch (error) { | ||
this.add(transaction, error); | ||
throw error; | ||
} | ||
}; | ||
/** | ||
* If the connection to the collector fails, retain as many as will fit without | ||
@@ -200,0 +175,0 @@ * overflowing the current error list. |
@@ -197,5 +197,5 @@ 'use strict'; | ||
default: | ||
logger.warn("Unrecognized version %s of Connect detected; not instrumenting.", | ||
version); | ||
logger.debug("Unrecognized version %s of Connect detected; not instrumenting.", | ||
version); | ||
} | ||
}; |
@@ -23,14 +23,15 @@ 'use strict'; | ||
return agent.tracer.transactionProxy(function wrappedHandler(request, response) { | ||
if (!agent.tracer.getState()) return listener.apply(this, arguments); | ||
var tracer = agent.tracer; | ||
return tracer.transactionProxy(function wrappedHandler(request, response) { | ||
if (!tracer.getTransaction()) return listener.apply(this, arguments); | ||
/* Needed for Connect and Express middlewares that monkeypatch request | ||
* and response via listeners. | ||
*/ | ||
agent.tracer.bindEmitter(request); | ||
agent.tracer.bindEmitter(response); | ||
tracer.bindEmitter(request); | ||
tracer.bindEmitter(response); | ||
var state = agent.tracer.getState() | ||
, transaction = state.getTransaction() | ||
, segment = state.getSegment().add(request.url, recordWeb) | ||
var transaction = tracer.getTransaction() | ||
, segment = tracer.addSegment(request.url, recordWeb) | ||
; | ||
@@ -60,4 +61,2 @@ | ||
state.setSegment(segment); | ||
return listener.apply(this, arguments); | ||
@@ -80,3 +79,3 @@ }); | ||
if (agent.tracer.getState() && !internalOnly) { | ||
if (agent.tracer.getTransaction() && !internalOnly) { | ||
// hostname & port logic pulled directly from node's 0.10 lib/http.js | ||
@@ -96,3 +95,3 @@ var hostname = options.hostname || options.host || DEFAULT_HOST; | ||
if (agent.tracer.getState()) { | ||
if (agent.tracer.getTransaction()) { | ||
instrumentOutbound(agent, requested, this.host, this.port); | ||
@@ -117,3 +116,4 @@ } | ||
shimmer.wrapMethod(http, 'http', 'createServer', function (createServer) { | ||
return function setDispatcher() { | ||
return function setDispatcher(requestListener) { | ||
/*jshint unused:false */ | ||
agent.environment.setDispatcher('http'); | ||
@@ -120,0 +120,0 @@ return createServer.apply(this, arguments); |
@@ -21,3 +21,3 @@ 'use strict'; | ||
if (!path) return logger.warn({route : route}, "No path found on Express route."); | ||
if (!path) return logger.debug({route : route}, "No path found on Express route."); | ||
@@ -67,4 +67,3 @@ // when route is a regexp, route.path will be a regexp | ||
logger.trace("Rendering Express %d view %s.", version, view); | ||
var state = tracer.getState(); | ||
if (!state) { | ||
if (!tracer.getTransaction()) { | ||
logger.trace("Express %d view %s rendered outside transaction, not measuring.", | ||
@@ -77,3 +76,3 @@ version, | ||
var name = VIEW.PREFIX + view + VIEW.RENDER | ||
, segment = state.getSegment().add(name, record) | ||
, segment = tracer.addSegment(name, record) | ||
, wrapped | ||
@@ -129,4 +128,3 @@ ; | ||
return function () { | ||
var state = tracer.getState(); | ||
if (!state) { | ||
if (!tracer.getTransaction()) { | ||
logger.trace("Express %d router called outside transaction.", version); | ||
@@ -137,3 +135,3 @@ return matchRequest.apply(this, arguments); | ||
var route = matchRequest.apply(this, arguments); | ||
nameFromRoute(state.getSegment(), route); | ||
nameFromRoute(tracer.getSegment(), route); | ||
return route; | ||
@@ -140,0 +138,0 @@ }; |
@@ -35,4 +35,3 @@ 'use strict'; | ||
return tracer.segmentProxy(function () { | ||
var state = tracer.getState(); | ||
if (!state) return command.apply(this, arguments); | ||
if (!tracer.getTransaction()) return command.apply(this, arguments); | ||
@@ -45,3 +44,3 @@ /* The 'command' function will be called with a single function argument. | ||
, name = MEMCACHE.OPERATION + (metacall.type || 'Unknown') | ||
, segment = state.getSegment().add(name, recordMemcache) | ||
, segment = tracer.addSegment(name, recordMemcache) | ||
, keys = wrapKeys(metacall) | ||
@@ -68,4 +67,2 @@ ; | ||
state.setSegment(segment); | ||
/* Memcached's call description includes a callback to apply when the | ||
@@ -72,0 +69,0 @@ * operation is concluded. Wrap that to trace the duration of the |
@@ -40,5 +40,7 @@ "use strict"; | ||
*/ | ||
function wrapNextObject(segment, tracer) { | ||
function wrapNextObject(tracer) { | ||
return function (nextObject) { | ||
return function wrappedNextObject() { | ||
if (!tracer.getTransaction()) return nextObject.apply(this, arguments); | ||
var args = tracer.slice(arguments) | ||
@@ -50,3 +52,3 @@ , last = args.length - 1 | ||
if (typeof callback === 'function') { | ||
if (typeof callback === 'function' && cursor.collection) { | ||
args[last] = tracer.callbackProxy(function (err, object) { | ||
@@ -58,2 +60,3 @@ var collection = cursor.collection.collectionName || 'unknown' | ||
, returned = callback.apply(this, arguments) | ||
, segment = tracer.getSegment() | ||
; | ||
@@ -81,12 +84,9 @@ | ||
function addMongoStatement(state, collection, operation) { | ||
function addMongoStatement(tracer, collection, operation) { | ||
var statement = new ParsedStatement(MONGODB.PREFIX, operation, collection) | ||
, recorder = statement.recordMetrics.bind(statement) | ||
, name = MONGODB.STATEMENT + collection + '/' + operation | ||
, next = state.getSegment().add(name, recorder) | ||
; | ||
state.setSegment(next); | ||
return next; | ||
return tracer.addSegment(name, recorder); | ||
} | ||
@@ -99,2 +99,7 @@ | ||
if (mongodb && mongodb.Cursor && mongodb.Cursor.prototype) { | ||
shimmer.wrapMethod(mongodb.Cursor.prototype, | ||
'mongodb.Cursor.prototype', 'nextObject', wrapNextObject(tracer)); | ||
} | ||
INSTRUMENTED_OPERATIONS.forEach(function (operation) { | ||
@@ -104,4 +109,3 @@ shimmer.wrapMethod(mongodb.Collection.prototype, | ||
return tracer.segmentProxy(function () { | ||
var state = tracer.getState() | ||
, collection = this.collectionName || 'unknown' | ||
var collection = this.collectionName || 'unknown' | ||
, args = tracer.slice(arguments) | ||
@@ -111,3 +115,3 @@ , terms = typeof args[0] === 'function' ? undefined : args[0] | ||
if (!state || args.length < 1) { | ||
if (!tracer.getTransaction() || args.length < 1) { | ||
logger.trace("Not tracing MongoDB %s.%s(); no transaction or parameters.", | ||
@@ -129,3 +133,6 @@ collection, operation); | ||
tracer.setCurrentSegmentType(MONGODB.PREFIX); | ||
var segment = addMongoStatement(state, collection, operation); | ||
var transaction = tracer.getTransaction() | ||
, segment = addMongoStatement(tracer, collection, operation) | ||
; | ||
if (agent.config.capture_params && typeof terms === 'object') { | ||
@@ -149,15 +156,7 @@ // clone terms | ||
args.push(callback); | ||
if (operation === 'find') { | ||
// no callback, so wrap the cursor iterator | ||
var cursor = command.apply(this, args); | ||
shimmer.wrapMethod(cursor, 'cursor', 'nextObject', | ||
wrapNextObject(segment, tracer)); | ||
return cursor; | ||
} | ||
else { | ||
if (operation !== 'find') { | ||
args.push(tracer.callbackProxy(function () { | ||
segment.end(); | ||
logger.trace("Tracing MongoDB %s.%s(%j) ended for transaction %s.", | ||
collection, operation, terms, state.getTransaction().id); | ||
collection, operation, terms, transaction.id); | ||
})); | ||
@@ -168,10 +167,3 @@ } | ||
if (operation === 'find') { | ||
args.push(tracer.callbackProxy(function (err, cursor) { | ||
if (cursor) { | ||
shimmer.wrapMethod(cursor, 'cursor', 'nextObject', | ||
wrapNextObject(segment, tracer)); | ||
} | ||
return callback.apply(this, arguments); | ||
})); | ||
args.push(tracer.callbackProxy(callback)); | ||
} | ||
@@ -184,3 +176,3 @@ else { | ||
logger.trace("Tracing MongoDB %s.%s(%j) ended for transaction %s.", | ||
collection, operation, terms, state.getTransaction().id); | ||
collection, operation, terms, transaction.id); | ||
@@ -187,0 +179,0 @@ return returned; |
@@ -23,4 +23,6 @@ 'use strict'; | ||
logger.trace("Potentially tracing node-mysql 2 query."); | ||
var state = tracer.getState(); | ||
if (!state || arguments.length < 1) return query.apply(this, arguments); | ||
if (!tracer.getTransaction() || arguments.length < 1) { | ||
return query.apply(this, arguments); | ||
} | ||
var transaction = tracer.getTransaction(); | ||
@@ -44,9 +46,8 @@ var actualSql, actualCallback; | ||
var ps = parseSql(MYSQL.PREFIX, actualSql); | ||
var wrapped = tracer.callbackProxy(actualCallback); | ||
var ps = parseSql(MYSQL.PREFIX, actualSql) | ||
, wrapped = tracer.callbackProxy(actualCallback) | ||
, name = MYSQL.STATEMENT + ps.model + '/' + ps.operation | ||
, segment = tracer.addSegment(name, ps.recordMetrics.bind(ps)) | ||
; | ||
var current = state.getSegment(); | ||
var segment = current.add(MYSQL.STATEMENT + ps.model + '/' + ps.operation, | ||
ps.recordMetrics.bind(ps)); | ||
// capture connection info for datastore instance metric | ||
@@ -59,4 +60,3 @@ if (this.config) { | ||
logger.trace("Adding node-mysql 2 query trace segment on transaction %d.", | ||
state.getTransaction().id); | ||
state.setSegment(segment); | ||
transaction.id); | ||
@@ -67,3 +67,3 @@ var returned = query.call(this, sql, values, wrapped); | ||
logger.trace("node-mysql 2 query finished for transaction %d.", | ||
state.getTransaction().id); | ||
transaction.id); | ||
}); | ||
@@ -87,14 +87,15 @@ | ||
logger.trace("Potentially tracing node-mysql 0.9 query."); | ||
var state = tracer.getState(); | ||
if (!state || arguments.length < 1) return query.apply(this, arguments); | ||
if (!tracer.getTransaction() || arguments.length < 1) { | ||
return query.apply(this, arguments); | ||
} | ||
var transaction = tracer.getTransaction(); | ||
logger.trace("Tracing node-mysql 0.9 query on transaction %d.", | ||
state.getTransaction().id); | ||
transaction.id); | ||
var args = tracer.slice(arguments); | ||
var ps = parseSql(MYSQL.PREFIX, args[0]); | ||
var args = tracer.slice(arguments) | ||
, ps = parseSql(MYSQL.PREFIX, args[0]) | ||
, name = MYSQL.STATEMENT + ps.model + '/' + ps.operation | ||
, segment = tracer.addSegment(name, ps.recordMetrics.bind(ps)) | ||
; | ||
var current = state.getSegment(); | ||
var segment = current.add(MYSQL.STATEMENT + ps.model + '/' + ps.operation, | ||
ps.recordMetrics.bind(ps)); | ||
// capture connection info for datastore instance metric | ||
@@ -104,4 +105,2 @@ segment.port = this.port; | ||
state.setSegment(segment); | ||
// find and wrap the callback | ||
@@ -119,3 +118,3 @@ if (args.length > 1 && typeof(args[args.length - 1]) === 'function') { | ||
logger.trace("node-mysql 0.9 query finished for transaction %d.", | ||
state.getTransaction().id); | ||
transaction.id); | ||
}); | ||
@@ -122,0 +121,0 @@ |
@@ -19,4 +19,3 @@ 'use strict'; | ||
return tracer.segmentProxy(function wrapped() { | ||
var state = tracer.getState(); | ||
if (!state || arguments.length < 1) { | ||
if (!tracer.getTransaction() || arguments.length < 1) { | ||
logger.trace("Not tracing Redis command due to no transaction state."); | ||
@@ -26,8 +25,9 @@ return send_command.apply(this, arguments); | ||
var args = tracer.slice(arguments) | ||
, name = REDIS.OPERATION + (args[0] || 'unknown') | ||
, segment = state.getSegment().add(name, recordRedis) | ||
, position = args.length - 1 | ||
, keys = args[1] | ||
, last = args[position] | ||
var transaction = tracer.getTransaction() | ||
, args = tracer.slice(arguments) | ||
, name = REDIS.OPERATION + (args[0] || 'unknown') | ||
, segment = tracer.addSegment(name, recordRedis) | ||
, position = args.length - 1 | ||
, keys = args[1] | ||
, last = args[position] | ||
; | ||
@@ -42,3 +42,3 @@ | ||
logger.trace("Adding Redis command trace segment transaction %d.", | ||
state.getTransaction().id); | ||
transaction.id); | ||
@@ -49,4 +49,2 @@ // capture connection info for datastore instance metric | ||
state.setSegment(segment); | ||
function finalize(target) { | ||
@@ -57,3 +55,3 @@ return function () { | ||
logger.trace("Redis command trace segment ended for transaction %d.", | ||
state.getTransaction().id); | ||
transaction.id); | ||
@@ -75,3 +73,3 @@ return returned; | ||
logger.trace("Redis command trace segment ended for transaction %d.", | ||
state.getTransaction().id); | ||
transaction.id); | ||
}); | ||
@@ -78,0 +76,0 @@ } |
@@ -56,4 +56,3 @@ 'use strict'; | ||
return function wrappedFind(req, res, callback) { | ||
var state = tracer.getState(); | ||
if (!state) { | ||
if (!tracer.getTransaction()) { | ||
logger.trace("Restify router invoked outside transaction."); | ||
@@ -64,3 +63,3 @@ return find.apply(this, arguments); | ||
var wrapped = function (error, route, context) { | ||
nameFromRoute(state.getSegment(), route, context); | ||
nameFromRoute(tracer.getSegment(), route, context); | ||
return callback(error, route, context); | ||
@@ -67,0 +66,0 @@ }; |
@@ -20,2 +20,4 @@ 'use strict'; | ||
this.id = setInterval(sampler, interval); | ||
// timer.unref only in 0.9+ | ||
if (this.id.unref) this.id.unref(); | ||
} | ||
@@ -22,0 +24,0 @@ |
@@ -15,2 +15,9 @@ 'use strict'; | ||
var CORE_INSTRUMENTATION = {http : 'http.js', https : 'http.js'}; | ||
var INSTRUMENTATION = fs.readdirSync( | ||
path.join(__dirname, 'instrumentation') | ||
).filter(function (name) { | ||
return path.extname(name) === '.js'; | ||
}).map(function (name) { | ||
return path.basename(name, '.js'); | ||
}); | ||
@@ -27,5 +34,2 @@ /** | ||
// polyfill for Node < 0.8 | ||
var existsSync = fs.existsSync || path.existsSync; | ||
/** | ||
@@ -47,11 +51,9 @@ * All instrumentation files must export the same interface: a single | ||
function _postLoad(agent, nodule, name) { | ||
var base = path.basename(name); | ||
// necessary to prevent instrument() from causing an infinite loop | ||
if (path.extname(name) !== '.js') { | ||
name = path.basename(name); | ||
var filename = path.join(__dirname, 'instrumentation', name + '.js'); | ||
if (existsSync(filename)) { | ||
logger.debug('Instrumenting %s.', name); | ||
instrument(agent, name, filename, nodule); | ||
} | ||
if (INSTRUMENTATION.indexOf(base) !== -1) { | ||
logger.debug('Instrumenting %s.', base); | ||
var filename = path.join(__dirname, 'instrumentation', base + '.js'); | ||
instrument(agent, base, filename, nodule); | ||
} | ||
@@ -235,14 +237,13 @@ | ||
* | ||
* If an instrumented module has a dependency on another instrumented | ||
* module, and multiple tests are being run in a single test suite | ||
* with their own setup and teardown between tests, it's possible | ||
* transitive dependencies will be unwrapped in the module cache | ||
* in-place (which needs to happen to prevent stale closures from | ||
* channeling instrumentation data to incorrect agents, but which means the | ||
* transitive dependencies won't get rewrapped the next time the parent | ||
* module is required). | ||
* If an instrumented module has a dependency on another instrumented module, | ||
* and multiple tests are being run in a single test suite with their own | ||
* setup and teardown between tests, it's possible transitive dependencies | ||
* will be unwrapped in the module cache in-place (which needs to happen to | ||
* prevent stale closures from channeling instrumentation data to incorrect | ||
* agents, but which means the transitive dependencies won't get rewrapped | ||
* the next time the parent module is required). | ||
* | ||
* Since this only applies in test code, it's not worth the drastic | ||
* monkeypatching to Module necessary to walk the list of child | ||
* modules and rewrap them. | ||
* monkeypatching to Module necessary to walk the list of child modules and | ||
* rewrap them. | ||
* | ||
@@ -249,0 +250,0 @@ * Use this to re-apply any applicable instrumentation. |
@@ -5,13 +5,5 @@ 'use strict'; | ||
, cls = require('continuation-local-storage') | ||
, dominion = require(path.join(__dirname, '..', 'dominion.js')) | ||
, State = require(path.join(__dirname, 'tracer', 'state.js')) | ||
, Transaction = require(path.join(__dirname, '..', 'transaction.js')) | ||
; | ||
/* Just in case something decides to use the production and | ||
* debugging tracers at the same time. | ||
*/ | ||
var namespace = process.namespaces.__NR_tracer; | ||
if (!namespace) namespace = cls.createNamespace("__NR_tracer"); | ||
/* | ||
@@ -23,6 +15,33 @@ * | ||
var ORIGINAL = '__NR_original' | ||
, TRACER = '__NR_tracer' | ||
, TYPE = '__NR_segment_type' | ||
; | ||
/* Just in case something decides to use the production and | ||
* debugging tracers at the same time. | ||
*/ | ||
var namespace = process.namespaces[TRACER]; | ||
if (!namespace) namespace = cls.createNamespace(TRACER); | ||
/** | ||
* Instead of eating the overhead of creating two separate async listeners | ||
* to handle CLS and error-tracing, reuse the existing CLS error callback. | ||
* | ||
* @param {Agent} agent The current agent instance. | ||
* @param {Namespace} namespace CLS instance. | ||
*/ | ||
function _patchErrorTracerOntoCLS(agent, namespace) { | ||
var callbacks = namespace && namespace.id && namespace.id.callbacks; | ||
if (callbacks && callbacks.error) { | ||
callbacks.error = function (domain, error) { | ||
var context = namespace.fromException(error); | ||
var transaction = context && context.transaction; | ||
agent.errors.add(transaction, error); | ||
if (domain) namespace.exit(domain); | ||
}; | ||
} | ||
} | ||
/** | ||
* EXECUTION TRACER | ||
@@ -55,44 +74,51 @@ * | ||
this.agent = agent; | ||
_patchErrorTracerOntoCLS(agent, namespace); | ||
} | ||
/** | ||
* Primary interface to the shared state / domains for the instrumentation. | ||
* Examine shared context to find any current transaction. | ||
* Filter out inactive transactions. | ||
* | ||
* @returns {State} The current state of the transaction tracer. | ||
* @returns {Transaction} The current transaction. | ||
*/ | ||
Tracer.prototype.getState = function () { | ||
return namespace.get('state'); | ||
Tracer.prototype.getTransaction = function () { | ||
var transaction = namespace.get('transaction'); | ||
if (transaction && transaction.isActive()) return transaction; | ||
}; | ||
Tracer.prototype.setTransaction = function (transaction) { | ||
namespace.set('transaction', transaction); | ||
}; | ||
/** | ||
* Examine shared context to find any current transaction. | ||
* Filter out inactive transactions. | ||
* Look up the currently active segment. | ||
* | ||
* @returns {Transaction} The current transaction. | ||
* @returns {Segment} Active segment, if set. | ||
*/ | ||
Tracer.prototype.getTransaction = function () { | ||
var state = namespace.get('state'); | ||
if (state) { | ||
var transaction = state.getTransaction(); | ||
if (transaction && transaction.isActive()) return transaction; | ||
} | ||
Tracer.prototype.getSegment = function () { | ||
return namespace.get('segment'); | ||
}; | ||
Tracer.prototype.createState = function (transaction, segment, handler) { | ||
var state = new State(transaction, segment, handler); | ||
if (dominion.available) dominion.add(this.agent.errors, state); | ||
return state; | ||
Tracer.prototype.setSegment = function (segment) { | ||
namespace.set('segment', segment); | ||
}; | ||
/** | ||
* Everything is connected, zude. | ||
* Create a new trace segment that depends on the current segment on the | ||
* active transaction trace. | ||
* | ||
* @param {State} state The encapsulated state of the trace. | ||
* @returns {Object} context The context to which CLS evaluation is bound. | ||
* @param {string} name The metric name for the new segment (can be | ||
* renamed by Segment.prototype.markAsWeb). | ||
* @param {function} recorder Function to be called when the segment is closed | ||
* and metrics are ready to be recorded. | ||
* | ||
* @returns {Segment} The newly-created segment. | ||
*/ | ||
Tracer.prototype.contextify = function (state) { | ||
var context = namespace.createContext(); | ||
context.state = state; | ||
Tracer.prototype.addSegment = function (name, recorder) { | ||
var current = namespace.get('segment'); | ||
var segment = current.add(name, recorder); | ||
namespace.set('segment', segment); | ||
return context; | ||
return segment; | ||
}; | ||
@@ -116,8 +142,14 @@ | ||
var transaction = self.getTransaction() || new Transaction(self.agent) | ||
, state = self.createState(transaction, transaction.getTrace().root, handler) | ||
, context = self.contextify(state) | ||
, proxied = this | ||
, args = self.slice(arguments) | ||
; | ||
return self.monitor(namespace.bind(state.call, context), | ||
this, arguments, transaction); | ||
var returned; | ||
namespace.bind(function () { | ||
self.setTransaction(transaction); | ||
self.setSegment(transaction.getTrace().root); | ||
returned = namespace.bind(handler).apply(proxied, args); | ||
}, Object.create(null))(); | ||
return returned; | ||
}; | ||
@@ -145,9 +177,5 @@ wrapped[ORIGINAL] = handler; | ||
// don't implicitly create transactions | ||
var state = self.getState(); | ||
if (!state) return self.monitor(handler, this, arguments); | ||
if (!self.getTransaction()) return handler.apply(this, arguments); | ||
state = self.createState(state.transaction, state.segment, handler); | ||
return self.monitor(namespace.bind(state.call, self.contextify(state)), | ||
this, arguments, state.transaction); | ||
return namespace.bind(handler, namespace.createContext()).apply(this, arguments); | ||
}; | ||
@@ -175,14 +203,5 @@ wrapped[ORIGINAL] = handler; | ||
// don't implicitly create transactions | ||
var state = this.getState(); | ||
var self = this; | ||
if (!state) return function monitored() { | ||
return self.monitor(handler, this, arguments); | ||
}; | ||
if (!this.getTransaction()) return handler; | ||
var context = this.contextify(state); | ||
var wrapped = function wrapCallbackInvocation() { | ||
state = self.createState(state.transaction, state.segment, handler); | ||
return self.monitor(namespace.bind(state.call, context), this, | ||
arguments, state.transaction); | ||
}; | ||
var wrapped = namespace.bind(handler, namespace.createContext()); | ||
wrapped[ORIGINAL] = handler; | ||
@@ -238,15 +257,2 @@ | ||
/** | ||
* Transaction tracer's lifecycle may not match error tracer's, so don't | ||
* hold onto direct references to it. | ||
*/ | ||
Tracer.prototype.monitor = function (handler, context, args, transaction) { | ||
return this.agent.errors.monitor( | ||
function () { | ||
return handler.apply(context, args); | ||
}, | ||
transaction | ||
); | ||
}; | ||
Tracer.prototype.slice = function slice(args) { | ||
@@ -253,0 +259,0 @@ /** |
@@ -5,21 +5,40 @@ 'use strict'; | ||
, cls = require('continuation-local-storage') | ||
, dominion = require(path.join(__dirname, '..', '..', 'dominion.js')) | ||
, util = require('util') | ||
, State = require(path.join(__dirname, 'state')) | ||
, NRTransaction = require(path.join(__dirname, '..', '..', 'transaction')) | ||
, NRTracer = require(path.join(__dirname, '..', 'tracer')) | ||
; | ||
/* | ||
* | ||
* CONSTANTS | ||
* | ||
*/ | ||
var TRACER = '__NR_tracer'; | ||
/* Just in case something decides to use the production and | ||
* debugging tracers at the same time. | ||
*/ | ||
var namespace = process.namespaces.__NR_tracer; | ||
if (!namespace) namespace = cls.createNamespace("__NR_tracer"); | ||
var namespace = process.namespaces[TRACER]; | ||
if (!namespace) namespace = cls.createNamespace(TRACER); | ||
/* | ||
/** | ||
* Instead of eating the overhead of creating two separate async listeners | ||
* to handle CLS and error-tracing, reuse the existing CLS error callback. | ||
* | ||
* CONSTANTS | ||
* | ||
* @param {Agent} agent The current agent instance. | ||
* @param {Namespace} namespace CLS instance. | ||
*/ | ||
var TYPE = '__NR_segment_type'; | ||
function _patchErrorTracerOntoCLS(agent, namespace) { | ||
var callbacks = namespace && namespace.id && namespace.id.callbacks; | ||
if (callbacks && callbacks.error) { | ||
callbacks.error = function (domain, error) { | ||
var context = namespace.fromException(error); | ||
var transaction = context && context.transaction; | ||
agent.errors.add(transaction, error); | ||
if (domain) namespace.exit(domain); | ||
}; | ||
} | ||
} | ||
/** | ||
@@ -188,32 +207,49 @@ * | ||
this.describer = new Describer(); | ||
_patchErrorTracerOntoCLS(agent, namespace); | ||
} | ||
Tracer.prototype.getState = function () { | ||
return namespace.get('state'); | ||
}; | ||
Tracer.prototype.getTransaction = function () { | ||
var state = namespace.get('state'); | ||
if (state) { | ||
var transaction = state.getTransaction(); | ||
if (transaction && transaction.isActive()) return transaction; | ||
var transaction = namespace.get('transaction'); | ||
if (transaction && transaction.value && transaction.value.isActive()) { | ||
return transaction.value; | ||
} | ||
}; | ||
Tracer.prototype.createState = function (transaction, segment, handler) { | ||
var state = new State(transaction, segment, handler, true); | ||
if (dominion.available) dominion.add(this.agent.errors, state); | ||
Tracer.prototype.setTransaction = function (transaction) { | ||
namespace.set('transaction', transaction); | ||
}; | ||
return state; | ||
Tracer.prototype.getSegment = function () { | ||
return namespace.get('segment') && namespace.get('segment').value; | ||
}; | ||
Tracer.prototype.enter = function (state, describer) { | ||
describer.traceCall('->', state.call); | ||
Tracer.prototype.setSegment = function (segment) { | ||
namespace.set('segment', segment); | ||
}; | ||
Tracer.prototype.exit = function (state, describer) { | ||
describer.traceCall('<-', state.call); | ||
Tracer.prototype.addSegment = function (name, recorder) { | ||
var current = this.getSegment() | ||
, segment = current.add(name, recorder) | ||
, transactionContainer = namespace.get('transaction') | ||
, transaction = transactionContainer.value | ||
, describer = transaction.describer | ||
, segmentContainer = this.traceSegment(transactionContainer, segment, describer) | ||
; | ||
this.setSegment(segmentContainer); | ||
return segment; | ||
}; | ||
Tracer.prototype.addTransaction = function (value, describer) { | ||
Tracer.prototype.enter = function (call, describer) { | ||
describer.traceCall('->', call); | ||
}; | ||
Tracer.prototype.exit = function (call, describer) { | ||
describer.traceCall('<-', call); | ||
}; | ||
Tracer.prototype.traceTransaction = function (value, describer) { | ||
this.numTransactions += 1; | ||
@@ -225,3 +261,3 @@ | ||
Tracer.prototype.addSegment = function (transaction, value, describer) { | ||
Tracer.prototype.traceSegment = function (transaction, value, describer) { | ||
describer.traceCreation('Segment'); | ||
@@ -231,3 +267,3 @@ return transaction.addSegment(value); | ||
Tracer.prototype.addCall = function (segment, value, describer) { | ||
Tracer.prototype.traceCall = function (segment, value, describer) { | ||
describer.traceCreation('Call'); | ||
@@ -252,3 +288,3 @@ return segment.addCall(value); | ||
var self = this; | ||
var wrapped = function () { | ||
var wrapped = function wrapTransactionInvocation() { | ||
var describer = self.describer.clone(); | ||
@@ -258,10 +294,11 @@ describer.traceWrapping('->', 'T inner'); | ||
// don't nest transactions, reuse existing ones | ||
var value = self.getTransaction() || new NRTransaction(self.agent) | ||
, transaction = self.addTransaction(value, describer) | ||
, segment = self.addSegment(transaction, value.getTrace().root, describer) | ||
, call = self.addCall(segment, handler, describer) | ||
, state = self.createState(transaction, segment, call) | ||
var transaction = self.getTransaction() || new NRTransaction(self.agent) | ||
, segment = transaction.getTrace().root | ||
, proxied = this | ||
, args = self.slice(arguments) | ||
, transactionContainer = self.traceTransaction(transaction, describer) | ||
, segmentContainer = self.traceSegment(transactionContainer, segment, describer) | ||
, callContainer = self.traceCall(segmentContainer, handler, describer) | ||
; | ||
state.describer = describer; | ||
/* NOICE HAX D00D | ||
@@ -272,10 +309,12 @@ * | ||
*/ | ||
value.state = state; | ||
transaction.describer = describer; | ||
self.enter(state, describer); | ||
var context = namespace.createContext(); | ||
context.state = state; | ||
var returned = self.monitor(namespace.bind(handler, context), this, | ||
arguments, transaction); | ||
self.exit(state, describer); | ||
self.enter(callContainer, describer); | ||
var returned; | ||
namespace.bind(function () { | ||
self.setTransaction(transactionContainer); | ||
self.setSegment(segmentContainer); | ||
returned = namespace.bind(handler).apply(proxied, args); | ||
}, Object.create(null))(); | ||
self.exit(callContainer, describer); | ||
@@ -306,24 +345,25 @@ describer.traceWrapping('<-', 'T inner'); | ||
var self = this; | ||
var wrapped = function () { | ||
var wrapped = function wrapSegmentInvocation() { | ||
// don't implicitly create transactions | ||
var state = self.getState(); | ||
if (!state) return self.monitor(handler, this, arguments); | ||
var transactionContainer = namespace.get('transaction'); | ||
if (!transactionContainer) return handler.apply(this, arguments); | ||
var describer = state.describer; | ||
var transaction = transactionContainer.value; | ||
if (!transaction.isActive()) return handler.apply(this, arguments); | ||
var describer = transaction.describer; | ||
describer.traceWrapping('->', 'S inner'); | ||
var segment = self.addSegment(state.transaction, state.segment.value, describer) | ||
, call = self.addCall(segment, handler, describer) | ||
var segment = self.getSegment() | ||
, segmentContainer = self.traceSegment(transactionContainer, segment, describer) | ||
, callContainer = self.traceCall(segmentContainer, handler, describer) | ||
, context = namespace.createContext() | ||
; | ||
state = new State(state.transaction, segment, call, true); | ||
state.describer = describer; | ||
state.transaction.value.state = state; | ||
context.segment = segmentContainer; | ||
context.call = callContainer; | ||
self.enter(state, describer); | ||
var context = namespace.createContext(); | ||
context.state = state; | ||
var returned = self.monitor(namespace.bind(handler, context), this, | ||
arguments, state.getTransaction()); | ||
self.exit(state, describer); | ||
self.enter(callContainer, describer); | ||
var returned = namespace.bind(handler, context).apply(this, arguments); | ||
self.exit(callContainer, describer); | ||
@@ -354,27 +394,27 @@ describer.traceWrapping('<-', 'S inner'); | ||
// don't implicitly create transactions | ||
var state = this.getState(); | ||
if (!state) return handler; | ||
var transaction = this.getTransaction(); | ||
if (!transaction) return handler; | ||
var describer = state.describer; | ||
var describer = transaction.describer; | ||
describer.traceWrapping('->', 'C outer'); | ||
var call = this.addCall(state.call.segment, handler, describer); | ||
var segmentContainer = namespace.get('segment') | ||
, callContainer = this.traceCall(segmentContainer, handler, describer) | ||
; | ||
var context = namespace.createContext(); | ||
context.segment = segmentContainer; | ||
context.call = callContainer; | ||
var self = this; | ||
var wrapped = function () { | ||
var wrapped = namespace.bind(function () { | ||
describer.traceWrapping('->', 'C inner'); | ||
state = new State(state.transaction, state.segment, call, true); | ||
state.describer = describer; | ||
state.transaction.value.state = state; | ||
self.enter(state, describer); | ||
var context = namespace.createContext(); | ||
context.state = state; | ||
var returned = self.monitor(namespace.bind(handler, context), this, | ||
arguments, state.transaction.value); | ||
self.exit(state, describer); | ||
self.enter(callContainer, describer); | ||
var returned = handler.apply(this, arguments); | ||
self.exit(callContainer, describer); | ||
describer.traceWrapping('<-', 'C inner'); | ||
return returned; | ||
}; | ||
}, context); | ||
@@ -385,57 +425,7 @@ describer.traceWrapping('<-', 'C outer'); | ||
Tracer.prototype.bindEmitter = function (emitter) { | ||
namespace.bindEmitter(emitter); | ||
}; | ||
Tracer.prototype.bindEmitter = NRTracer.prototype.bindEmitter; | ||
Tracer.prototype.setCurrentSegmentType = NRTracer.prototype.setCurrentSegmentType; | ||
Tracer.prototype.isCurrentSegmentType = NRTracer.prototype.isCurrentSegmentType; | ||
Tracer.prototype.slice = NRTracer.prototype.slice; | ||
Tracer.prototype.setCurrentSegmentType = function (type) { | ||
// only add a cleaner if there isn't one set already | ||
if (!namespace.get(TYPE)) process.nextTick(function () { | ||
namespace.set(TYPE, undefined); | ||
}); | ||
namespace.set(TYPE, type); | ||
}; | ||
Tracer.prototype.isCurrentSegmentType = function (type) { | ||
return namespace.get(TYPE) === type; | ||
}; | ||
/** | ||
* Transaction tracer's lifecycle may not match error tracer's, so don't | ||
* hold onto direct references to it. | ||
*/ | ||
Tracer.prototype.monitor = function (handler, context, args, transaction) { | ||
return this.agent.errors.monitor( | ||
function () { | ||
return handler.apply(context, args); | ||
}, | ||
transaction | ||
); | ||
}; | ||
Tracer.prototype.slice = function slice(args) { | ||
/** | ||
* Usefully nerfed version of slice for use in instrumentation. Way faster | ||
* than using [].slice.call, and maybe putting it in here (instead of the | ||
* same module context where it will be used) will make it faster by | ||
* defeating inlining. | ||
* | ||
* http://jsperf.com/array-slice-call-arguments-2 | ||
* | ||
* for untrustworthy benchmark numbers. Only useful for copying whole | ||
* arrays, and really only meant to be used with the arguments arraylike. | ||
* | ||
* Also putting this comment inside the function in an effort to defeat | ||
* inlining. | ||
*/ | ||
var length = args.length | ||
, array = [] | ||
, i | ||
; | ||
for (i = 0; i < length; i++) { | ||
array[i] = args[i]; | ||
} | ||
return array; | ||
}; | ||
module.exports = Tracer; |
@@ -19,5 +19,5 @@ 'use strict'; | ||
var state = agent.tracer.getState() | ||
, name = NAMES.EXTERNAL.PREFIX + hostname + urltils.scrub(request.path) | ||
, segment = state.getSegment().add(name, recordExternal(hostname, 'http')) | ||
var transaction = agent.tracer.getTransaction() | ||
, name = NAMES.EXTERNAL.PREFIX + hostname + urltils.scrub(request.path) | ||
, segment = agent.tracer.addSegment(name, recordExternal(hostname, 'http')) | ||
; | ||
@@ -37,3 +37,3 @@ | ||
request.once('error', function (error) { | ||
agent.errors.add(state.getTransaction(), error); | ||
agent.errors.add(transaction, error); | ||
segment.end(); | ||
@@ -48,6 +48,4 @@ }); | ||
agent.tracer.bindEmitter(request); | ||
state.setSegment(segment); | ||
} | ||
module.exports = instrumentOutbound; |
25
NEWS.md
@@ -0,1 +1,26 @@ | ||
### v1.2.0 (2013-12-07): | ||
* Before, there were certain circumstances under which an application would | ||
crash without New Relic installed, but wouldn't crash with it. This has been | ||
fixed, and the error tracer has been considerably simplified. | ||
* Added a security policy. See the new section in README.md or read | ||
SECURITY.md. | ||
* Future-proofed the MongoDB instrumentation and prevented the module from | ||
breaking GridFS. | ||
* Made a small tweak that should reduce the amount of blocking file I/O done by | ||
the module. | ||
* The module's instrumentation and harvest cycle will now not hold the process | ||
open in Node 0.9+. This should make it easier for processes to shut | ||
themselves down cleanly with New Relic running. | ||
* The environment information gatherer will no longer crash if it tries to read | ||
a directory where it's expecting a file. | ||
* Errors thrown during the execution of Express routes or Connect middlewares | ||
that were attached to requests that ended in HTTP status codes configured to | ||
be ignored by default will now be ignored correctly. | ||
* Made the module play nicer with Node's REPL. It no longer assumes that an | ||
application necessarily has a main module. | ||
* A few tweaks were made to support the CoolBeans dependency injection | ||
framework. | ||
* Several log messages were demoted to a less chatty level. | ||
### v1.1.1 (2013-11-08): | ||
@@ -2,0 +27,0 @@ |
@@ -69,4 +69,9 @@ { | ||
}, | ||
"homepage": "https://github.com/trentm/node-bunyan", | ||
"_id": "bunyan@0.14.6", | ||
"_from": "bunyan@0.14.6" | ||
"dist": { | ||
"shasum": "37047c9e5c4f8bfc2eda66d9c49f261580782a4c" | ||
}, | ||
"_from": "bunyan@0.14.6", | ||
"_resolved": "https://registry.npmjs.org/bunyan/-/bunyan-0.14.6.tgz" | ||
} |
'use strict'; | ||
var assert = require('assert'); | ||
var shimmer = require('shimmer'); | ||
var assert = require('assert'); | ||
var wrapEmitter = require('emitter-listener'); | ||
@@ -12,2 +12,3 @@ /* | ||
var CONTEXTS_SYMBOL = 'cls@contexts'; | ||
var ERROR_SYMBOL = 'error@context'; | ||
@@ -47,2 +48,6 @@ // load polyfill if native support is unavailable | ||
} | ||
catch (exception) { | ||
exception[ERROR_SYMBOL] = context; | ||
throw exception; | ||
} | ||
finally { | ||
@@ -61,2 +66,6 @@ this.exit(context); | ||
} | ||
catch (exception) { | ||
exception[ERROR_SYMBOL] = context; | ||
throw exception; | ||
} | ||
finally { | ||
@@ -124,5 +133,15 @@ self.exit(context); | ||
shimmer.wrapEmitter(emitter, attach, bind); | ||
wrapEmitter(emitter, attach, bind); | ||
}; | ||
/** | ||
* If an error comes out of a namespace, it will have a context attached to it. | ||
* This function knows how to find it. | ||
* | ||
* @param {Error} exception Possibly annotated error. | ||
*/ | ||
Namespace.prototype.fromException = function (exception) { | ||
return exception[ERROR_SYMBOL]; | ||
}; | ||
function get(name) { | ||
@@ -129,0 +148,0 @@ return namespaces[name]; |
@@ -206,9 +206,2 @@ var wrap = require('shimmer').wrap; | ||
/** | ||
* The error handler in the error-checker for old Node must rethrow to give | ||
* domains and other uncaughtException handlers a chance to fire, but there's | ||
* nothing for the uncaughtException handler to do. | ||
*/ | ||
var threw = false; | ||
/** | ||
* If an error handler in asyncWrap throws, the process must die. Under 0.8 | ||
@@ -227,7 +220,2 @@ * and earlier the only way to put a bullet through the head of the process | ||
asyncCatcher = function uncaughtCatcher(er) { | ||
if (threw) { | ||
threw = false; | ||
return; | ||
} | ||
// going down hard | ||
@@ -278,2 +266,10 @@ if (errorThrew) throw er; | ||
return function () { | ||
/*jshint maxdepth:4*/ | ||
// after() handlers don't run if threw | ||
var threw = false; | ||
// ...unless the error is handled | ||
var handled = false; | ||
/* More than one listener can end up inside these closures, so save the | ||
@@ -305,3 +301,3 @@ * current listeners on a stack. | ||
catch (er) { | ||
var handled = false; | ||
threw = true; | ||
for (var i = 0; i < length; ++i) { | ||
@@ -320,23 +316,29 @@ var error = listeners[i].callbacks.error; | ||
// back to the previous listener list on the stack | ||
listeners = listenerStack.pop(); | ||
if (!handled) { | ||
// having an uncaughtException handler here alters crash semantics | ||
process.removeListener('uncaughtException', asyncCatcher); | ||
process._originalNextTick(function () { | ||
process.addListener('uncaughtException', asyncCatcher); | ||
}); | ||
if (handled) return; | ||
threw = true; | ||
throw er; | ||
throw er; | ||
} | ||
} | ||
finally { | ||
/* | ||
* after handlers (not run if original throws) | ||
*/ | ||
if (!threw || handled) { | ||
inAsyncTick = true; | ||
for (i = 0; i < length; ++i) { | ||
var after = list[i].callbacks && list[i].callbacks.after; | ||
if (typeof after === 'function') after(this, values[i], returned); | ||
} | ||
inAsyncTick = false; | ||
} | ||
/* | ||
* after handlers (not run if original throws) | ||
*/ | ||
inAsyncTick = true; | ||
for (i = 0; i < length; ++i) { | ||
var after = list[i].callbacks && list[i].callbacks.after; | ||
if (typeof after === 'function') after(this, values[i], returned); | ||
// back to the previous listener list on the stack | ||
listeners = listenerStack.pop(); | ||
} | ||
inAsyncTick = false; | ||
// back to the previous listener list on the stack | ||
listeners = listenerStack.pop(); | ||
@@ -348,3 +350,3 @@ return returned; | ||
// will be the first to fire if async-listener is the first module loaded | ||
process.on('uncaughtException', asyncCatcher); | ||
process.addListener('uncaughtException', asyncCatcher); | ||
} | ||
@@ -351,0 +353,0 @@ |
@@ -90,2 +90,7 @@ 'use strict'; | ||
// need unwrapped nextTick for use within < 0.9 async error handling | ||
if (!process._fatalException) { | ||
process._originalNextTick = process.nextTick; | ||
} | ||
var processors = ['nextTick']; | ||
@@ -92,0 +97,0 @@ if (process._nextDomainTick) processors.push('_nextDomainTick'); |
{ | ||
"name": "async-listener", | ||
"version": "0.4.3", | ||
"version": "0.4.5", | ||
"description": "Polyfill exporting trevnorris's 0.11+ asyncListener API.", | ||
@@ -39,6 +39,6 @@ "author": { | ||
"engines": { | ||
"node": "<0.11.7" | ||
"node": "<=0.11.8" | ||
}, | ||
"dependencies": { | ||
"shimmer": "~0.9" | ||
"shimmer": "1.0.0" | ||
}, | ||
@@ -50,4 +50,9 @@ "devDependencies": { | ||
"readmeFilename": "README.md", | ||
"_id": "async-listener@0.4.3", | ||
"_from": "async-listener@0.4.3" | ||
"homepage": "https://github.com/othiym23/async-listener", | ||
"_id": "async-listener@0.4.5", | ||
"dist": { | ||
"shasum": "ea246828799b39db40af0822f6acd5efbd3044ab" | ||
}, | ||
"_from": "async-listener@0.4.5", | ||
"_resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.4.5.tgz" | ||
} |
{ | ||
"name": "continuation-local-storage", | ||
"version": "2.5.2", | ||
"version": "2.6.2", | ||
"description": "userland implementation of https://github.com/joyent/node/issues/5243", | ||
@@ -43,7 +43,7 @@ "main": "context.js", | ||
"optionalDependencies": { | ||
"async-listener": "0.4.3" | ||
"async-listener": "0.4.5" | ||
}, | ||
"dependencies": { | ||
"shimmer": "~0.9", | ||
"async-listener": "0.4.3" | ||
"emitter-listener": "1.0.1", | ||
"async-listener": "0.4.5" | ||
}, | ||
@@ -55,4 +55,9 @@ "readme": "[![NPM](https://nodei.co/npm/continuation-local-storage.png?downloads=true&stars=true)](https://nodei.co/npm/continuation-local-storage/)\n\n# Continuation-Local Storage\n\nContinuation-local storage works like thread-local storage in threaded\nprogramming, but is based on chains of Node-style callbacks instead of threads.\nThe standard Node convention of functions calling functions is very similar to\nsomething called [\"continuation-passing style\"][cps] in functional programming,\nand the name comes from the way this module allows you to set and get values\nthat are scoped to the lifetime of these chains of function calls.\n\nSuppose you're writing a module that fetches a user and adds it to a session\nbefore calling a function passed in by a user to continue execution:\n\n```javascript\n// setup.js\n\nvar createNamespace = require('continuation-local-storage').createNamespace;\nvar session = createNamespace('my session');\n\nvar db = require('./lib/db.js');\n\nfunction start(options, next) {\n db.fetchUserById(options.id, function (error, user) {\n if (error) return next(error);\n\n session.set('user', user);\n\n next();\n });\n}\n```\n\nLater on in the process of turning that user's data into an HTML page, you call\nanother function (maybe defined in another module entirely) that wants to fetch\nthe value you set earlier:\n\n```javascript\n// send_response.js\n\nvar getNamespace = require('continuation-local-storage').getNamespace;\nvar session = getNamespace('my session');\n\nvar render = require('./lib/render.js')\n\nfunction finish(response) {\n var user = session.get('user');\n render({user: user}).pipe(response);\n}\n```\n\nWhen you set values in continuation-local storage, those values are accessible\nuntil all functions called from the original function – synchronously or\nasynchronously – have finished executing. This includes callbacks passed to\n`process.nextTick` and the [timer functions][] ([setImmediate][],\n[setTimeout][], and [setInterval][]), as well as callbacks passed to\nasynchronous functions that call native functions (such as those exported from\nthe `fs`, `dns`, `zlib` and `crypto` modules).\n\nA simple rule of thumb is anywhere where you might have set a property on the\n`request` or `response` objects in an HTTP handler, you can (and should) now\nuse continuation-local storage. This API is designed to allow you extend the\nscope of a variable across a sequence of function calls, but with values\nspecific to each sequence of calls.\n\nValues are grouped into namespaces, created with `createNamespace()`. Sets of\nfunction calls are grouped together by calling them within the function passed\nto `.run()` on the namespace object. Calls to `.run()` can be nested, and each\nnested context this creates has its own copy of the set of values from the\nparent context. When a function is making multiple asynchronous calls, this\nallows each child call to get, set, and pass along its own context without\noverwriting the parent's.\n\nA simple, annotated example of how this nesting behaves:\n\n```javascript\nvar createNamespace = require('contination-local-storage').createNamespace;\n\nvar writer = createNamespace('writer');\nwriter.set('value', 0);\n\nfunction requestHandler() {\n writer.run(function(outer) {\n // writer.get('value') returns 0\n // outer.value is 0\n writer.set('value', 1);\n // writer.get('value') returns 1\n // outer.value is 1\n process.nextTick(function() {\n // writer.get('value') returns 1\n // outer.value is 1\n writer.run(function(inner) {\n // writer.get('value') returns 1\n // outer.value is 1\n // inner.value is 1\n writer.set('value', 2);\n // writer.get('value') returns 2\n // outer.value is 1\n // inner.value is 2\n });\n });\n });\n\n setTimeout(function() {\n // runs with the default context, because nested contexts have ended\n console.log(writer.get('value')); // prints 0\n }, 1000);\n}\n```\n\n## cls.createNamespace(name)\n\n* return: {Namespace}\n\nEach application wanting to use continuation-local values should create its own\nnamespace. Reading from (or, more significantly, writing to) namespaces that\ndon't belong to you is a faux pas.\n\n## cls.getNamespace(name)\n\n* return: {Namespace}\n\nLook up an existing namespace.\n\n## cls.destroyNamespace(name)\n\nDispose of an existing namespace. WARNING: be sure to dispose of any references\nto destroyed namespaces in your old code, as contexts associated with them will\nno longer be propagated.\n\n## cls.reset()\n\nCompletely reset all continuation-local storage namespaces. WARNING: while this\nwill stop the propagation of values in any existing namespaces, if there are\nremaining references to those namespaces in code, the associated storage will\nstill be reachable, even though the associated state is no longer being updated.\nMake sure you clean up any references to destroyed namespaces yourself.\n\n## process.namespaces\n\n* return: dictionary of {Namespace} objects\n\nContinuation-local storage has a performance cost, and so it isn't enabled\nuntil the module is loaded for the first time. Once the module is loaded, the\ncurrent set of namespaces is available in `process.namespaces`, so library code\nthat wants to use continuation-local storage only when it's active should test\nfor the existence of `process.namespaces`.\n\n## Class: Namespace\n\nApplication-specific namespaces group values local to the set of functions\nwhose calls originate from a callback passed to `namespace.run()` or\n`namespace.bind()`.\n\n### namespace.active\n\n* return: the currently active context on a namespace\n\n### namespace.set(key, value)\n\n* return: `value`\n\nSet a value on the current continuation context.\n\n### namespace.get(key)\n\n* return: the requested value, or `undefined`\n\nLook up a value on the current continuation context. Recursively searches from\nthe innermost to outermost nested continuation context for a value associated\nwith a given key.\n\n### namespace.run(callback)\n\n* return: the context associated with that callback\n\nCreate a new context on which values can be set or read. Run all the functions\nthat are called (either directly, or indirectly through asynchronous functions\nthat take callbacks themselves) from the provided callback within the scope of\nthat namespace. The new context is passed as an argument to the callback\nwhne it's called.\n\n### namespace.bind(callback, [context])\n\n* return: a callback wrapped up in a context closure\n\nBind a function to the specified namespace. Works analogously to\n`Function.bind()` or `domain.bind()`. If context is omitted, it will default to\nthe currently active context in the namespace.\n\n### namespace.bindEmitter(emitter)\n\nBind an EventEmitter to a namespace. Operates similarly to `domain.add`, with a\nless generic name and the additional caveat that unlike domains, namespaces\nnever implicitly bind EventEmitters to themselves when they're created within\nthe context of an active namespace.\n\nThe most likely time you'd want to use this is when you're using Express or\nConnect and want to make sure your middleware execution plays nice with CLS, or\nare doing other things with HTTP listeners:\n\n```javascript\nhttp.createServer(function (req, res) {\n writer.add(req);\n writer.add(res);\n\n // do other stuff, some of which is asynchronous\n});\n```\n\n### namespace.createContext()\n\n* return: a context cloned from the currently active context\n\nUse this with `namespace.bind()`, if you want to have a fresh context at invocation time,\nas opposed to binding time:\n\n```javascript\nfunction doSomething(p) {\n console.log(\"%s = %s\", p, ns.get(p));\n}\n\nfunction bindLater(callback) {\n return writer.bind(callback, writer.createContext());\n}\n\nsetInterval(function () {\n var bound = bindLater(doSomething);\n bound('test');\n}, 100);\n```\n\n## context\n\nA context is a plain object created using the enclosing context as its prototype.\n\n[timer functions]: timers.html\n[setImmediate]: timers.html#timers_setimmediate_callback_arg\n[setTimeout]: timers.html#timers_settimeout_callback_delay_arg\n[setInterval]: timers.html#timers_setinterval_callback_delay_arg\n[cps]: http://en.wikipedia.org/wiki/Continuation-passing_style\n", | ||
}, | ||
"_id": "continuation-local-storage@2.5.2", | ||
"_from": "continuation-local-storage@2.5.2" | ||
"homepage": "https://github.com/othiym23/node-continuation-local-storage", | ||
"_id": "continuation-local-storage@2.6.2", | ||
"dist": { | ||
"shasum": "904a9a8dadf4178c8aaf30514f5fe2ba023651cf" | ||
}, | ||
"_from": "continuation-local-storage@2.6.2", | ||
"_resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-2.6.2.tgz" | ||
} |
@@ -21,2 +21,3 @@ 'use strict'; | ||
cls.destroyNamespace('test'); | ||
t.end(); | ||
@@ -37,1 +38,74 @@ }); | ||
}); | ||
test("synchronous throw attaches the context", function (t) { | ||
t.plan(3); | ||
var namespace = cls.createNamespace('cls@synchronous'); | ||
namespace.set('value', 'transaction clear'); | ||
try { | ||
namespace.run(function () { | ||
namespace.set('value', 'transaction set'); | ||
throw new Error('cls@synchronous explosion'); | ||
}); | ||
} | ||
catch (e) { | ||
t.ok(namespace.fromException(e), "context was attached to error"); | ||
t.equal(namespace.fromException(e)['value'], 'transaction set', | ||
"found the inner value"); | ||
} | ||
t.equal(namespace.get('value'), 'transaction clear', "everything was reset"); | ||
cls.destroyNamespace('cls@synchronous'); | ||
}); | ||
test("throw in process.nextTick attaches the context", function (t) { | ||
t.plan(3); | ||
var namespace = cls.createNamespace('cls@nexttick'); | ||
var d = domain.create(); | ||
namespace.set('value', 'transaction clear'); | ||
d.on('error', function (e) { | ||
t.ok(namespace.fromException(e), "context was attached to error"); | ||
t.equal(namespace.fromException(e)['value'], 'transaction set', | ||
"found the inner value"); | ||
cls.destroyNamespace('cls@nexttick'); | ||
}); | ||
// tap is only trying to help | ||
process.nextTick(d.bind(function () { | ||
namespace.run(function () { | ||
namespace.set('value', 'transaction set'); | ||
throw new Error("cls@nexttick explosion"); | ||
}); | ||
})); | ||
t.equal(namespace.get('value'), 'transaction clear', "everything was reset"); | ||
}); | ||
test("throw in setTimeout attaches the context", function (t) { | ||
t.plan(3); | ||
var namespace = cls.createNamespace('cls@nexttick'); | ||
var d = domain.create(); | ||
namespace.set('value', 'transaction clear'); | ||
d.on('error', function (e) { | ||
t.ok(namespace.fromException(e), "context was attached to error"); | ||
t.equal(namespace.fromException(e)['value'], 'transaction set', | ||
"found the inner value"); | ||
cls.destroyNamespace('cls@nexttick'); | ||
}); | ||
// tap is only trying to help | ||
setTimeout(d.bind(function () { | ||
namespace.run(function () { | ||
namespace.set('value', 'transaction set'); | ||
throw new Error("cls@nexttick explosion"); | ||
}); | ||
})); | ||
t.equal(namespace.get('value'), 'transaction clear', "everything was reset"); | ||
}); |
{ | ||
"name": "newrelic", | ||
"version": "1.1.1", | ||
"version": "1.2.0", | ||
"author": "New Relic Node.js agent team <nodejs@newrelic.com>", | ||
@@ -49,3 +49,3 @@ "licenses": [ | ||
"bunyan": "0.14.6", | ||
"continuation-local-storage": "2.5.2" | ||
"continuation-local-storage": "2.6.2" | ||
}, | ||
@@ -52,0 +52,0 @@ "bundledDependencies": [ |
@@ -20,2 +20,3 @@ [![npm status badge](https://nodei.co/npm/newrelic.png?stars=true&downloads=true)](https://nodei.co/npm/newrelic/) | ||
* [Configuration](#configuring-the-agent) | ||
* [Security](#security) | ||
* [Contributions](#contributions) | ||
@@ -363,2 +364,9 @@ * [Known issues](#known-issues) | ||
## Security | ||
We take security (and the protection of your and your users' privacy) very | ||
seriously. See SECURITY.md for details, but the brief version is that if you | ||
feel you've found a security issue in New Relic for Node, contact us at | ||
security@newrelic.com. | ||
## Contributions | ||
@@ -365,0 +373,0 @@ |
@@ -66,2 +66,47 @@ 'use strict'; | ||
describe("when handling connection failures", function () { | ||
var agent | ||
, error | ||
; | ||
function HulkObject() {} | ||
HulkObject.prototype.toJSON = function () { | ||
throw new Error("You wouldn't like me when I'm serialized."); | ||
}; | ||
beforeEach(function () { | ||
agent = helper.loadMockedAgent(); | ||
error = new Error('test error'); | ||
error.stylee = new HulkObject(); | ||
}); | ||
afterEach(function () { | ||
helper.unloadAgent(agent); | ||
}); | ||
it("shouldn't blow up when merging metrics with no error", function () { | ||
expect(function () { | ||
agent.mergeMetrics([0, 0, 0, agent.metrics], null); | ||
}).not.throws(); | ||
}); | ||
it("shouldn't blow up when merging metrics with a weird error", function () { | ||
expect(function () { | ||
agent.mergeMetrics([0, 0, 0, agent.metrics], error); | ||
}).not.throws(); | ||
}); | ||
it("shouldn't blow up when merging errors with no error", function () { | ||
expect(function () { | ||
agent.mergeErrors([], null); | ||
}).not.throws(); | ||
}); | ||
it("shouldn't blow up when merging errors with a weird error", function () { | ||
expect(function () { | ||
agent.mergeErrors([], error); | ||
}).not.throws(); | ||
}); | ||
}); | ||
describe("with a stubbed collector connection", function () { | ||
@@ -68,0 +113,0 @@ var agent |
@@ -68,6 +68,4 @@ 'use strict'; | ||
helper.runInTransaction(agent, function (transaction) { | ||
// set up web segment | ||
var state = agent.tracer.getState(); | ||
// grab segment | ||
segment = state.getSegment().add(NAME); | ||
segment = agent.tracer.addSegment(NAME); | ||
@@ -110,5 +108,3 @@ // HTTP instrumentation sets URL as soon as it knows it | ||
helper.runInTransaction(agent, function (transaction) { | ||
var state = agent.tracer.getState(); | ||
segment = state.getSegment().add(NAME); | ||
segment = agent.tracer.addSegment(NAME); | ||
transaction.url = URL; | ||
@@ -139,5 +135,3 @@ transaction.verb = 'GET'; | ||
helper.runInTransaction(agent, function (transaction) { | ||
var state = agent.tracer.getState(); | ||
state.getSegment().add(NAME); | ||
agent.tracer.addSegment(NAME); | ||
transaction.url = URL; | ||
@@ -166,5 +160,3 @@ transaction.verb = 'GET'; | ||
helper.runInTransaction(agent, function (transaction) { | ||
var state = agent.tracer.getState(); | ||
segment = state.getSegment().add(NAME); | ||
segment = agent.tracer.addSegment(NAME); | ||
transaction.url = URL; | ||
@@ -196,6 +188,4 @@ transaction.verb = 'GET'; | ||
helper.runInTransaction(agent, function (transaction) { | ||
// set up web segment | ||
var state = agent.tracer.getState(); | ||
// grab segment | ||
segment = state.getSegment().add(NAME); | ||
segment = agent.tracer.addSegment(NAME); | ||
@@ -238,4 +228,3 @@ // HTTP instrumentation sets URL as soon as it knows it | ||
helper.runInTransaction(agent, function (transaction) { | ||
var state = agent.tracer.getState(); | ||
segment = state.getSegment().add(NAME); | ||
segment = agent.tracer.addSegment(NAME); | ||
@@ -266,5 +255,3 @@ transaction.url = URL; | ||
helper.runInTransaction(agent, function (transaction) { | ||
var state = agent.tracer.getState(); | ||
segment = state.getSegment().add(NAME); | ||
segment = agent.tracer.addSegment(NAME); | ||
transaction.url = URL; | ||
@@ -292,5 +279,3 @@ transaction.verb = 'GET'; | ||
helper.runInTransaction(agent, function (transaction) { | ||
var state = agent.tracer.getState(); | ||
segment = state.getSegment().add(NAME); | ||
segment = agent.tracer.addSegment(NAME); | ||
transaction.url = URL; | ||
@@ -375,5 +360,3 @@ transaction.verb = 'GET'; | ||
helper.runInTransaction(agent, function (transaction) { | ||
var state = agent.tracer.getState(); | ||
segment = state.getSegment().add(NAME); | ||
segment = agent.tracer.addSegment(NAME); | ||
transaction.url = URL; | ||
@@ -400,5 +383,3 @@ transaction.verb = 'GET'; | ||
helper.runInTransaction(agent, function (transaction) { | ||
var state = agent.tracer.getState(); | ||
segment = state.getSegment().add(NAME); | ||
segment = agent.tracer.addSegment(NAME); | ||
transaction.url = '/test/31337/related'; | ||
@@ -481,5 +462,3 @@ transaction.verb = 'GET'; | ||
helper.runInTransaction(agent, function (transaction) { | ||
var state = agent.tracer.getState(); | ||
segment = state.getSegment().add(NAME); | ||
segment = agent.tracer.addSegment(NAME); | ||
transaction.url = URL; | ||
@@ -486,0 +465,0 @@ transaction.verb = 'GET'; |
'use strict'; | ||
var path = require('path') | ||
, spawn = require('child_process').spawn | ||
, chai = require('chai') | ||
@@ -127,2 +128,26 @@ , expect = chai.expect | ||
}); | ||
it("should not crash when given a file in NODE_PATH", function (done) { | ||
var env = { | ||
NODE_PATH : path.join(__dirname, "environment.test.js"), | ||
PATH : process.env.PATH | ||
}; | ||
var opt = { | ||
env : env, | ||
stdio : 'inherit', | ||
cwd : path.join(__dirname, '..') | ||
}; | ||
var exec = process.argv[0] | ||
, args = [path.join(__dirname, 'helpers', 'environment.child.js')] | ||
, proc = spawn(exec, args, opt) | ||
; | ||
proc.on('exit', function (code) { | ||
expect(code).equal(0); | ||
done(); | ||
}); | ||
}); | ||
}); |
@@ -9,3 +9,2 @@ 'use strict'; | ||
, config = require(path.join(__dirname, '..', 'lib', 'config.default')) | ||
, dominion = require(path.join(__dirname, '..', 'lib', 'dominion')) | ||
, ErrorTracer = require(path.join(__dirname, '..', 'lib', 'error')) | ||
@@ -116,2 +115,12 @@ , Transaction = require(path.join(__dirname, '..', 'lib', 'transaction')) | ||
}); | ||
it("should ignore 404 errors for transactions with exceptions attached", function () { | ||
tracer.onTransactionFinished(createTransaction(agent, 400), agent.metrics); | ||
// 404 errors are ignored by default | ||
var special = createTransaction(agent, 404); | ||
special.exceptions.push(new Error('ignored')); | ||
tracer.onTransactionFinished(special, agent.metrics); | ||
expect(tracer.errorCount).equal(1); | ||
}); | ||
}); | ||
@@ -684,40 +693,2 @@ | ||
describe("when monitoring function application for errors", function () { | ||
var agent | ||
, transaction | ||
, mochaHandlers | ||
; | ||
beforeEach(function () { | ||
agent = helper.loadMockedAgent(); | ||
transaction = new Transaction(agent); | ||
mochaHandlers = helper.onlyDomains(); | ||
}); | ||
afterEach(function () { | ||
transaction.end(); | ||
helper.unloadAgent(agent); | ||
process._events['uncaughtException'] = mochaHandlers; | ||
}); | ||
it("should rethrow the exception", function () { | ||
var testFunction = function () { | ||
var uninitialized; | ||
uninitialized.explosion.happens.here = "fabulous"; | ||
}; | ||
expect(function () { | ||
tracer.monitor(testFunction, transaction); | ||
}).throws(TypeError); | ||
}); | ||
it("should return the correct value", function () { | ||
var safeFunction = function (val) { | ||
return val * val; | ||
}; | ||
expect(tracer.monitor(safeFunction.bind(null, 3), transaction)).equal(9); | ||
}); | ||
}); | ||
describe("when merging from failed collector delivery", function () { | ||
@@ -739,103 +710,91 @@ it("shouldn't crash on null errors", function () { | ||
if (dominion.available) { | ||
describe("when domains are available", function () { | ||
var mochaHandlers | ||
, agent | ||
, transaction | ||
, domain | ||
, active | ||
, json | ||
; | ||
describe("when using the async listener", function () { | ||
var mochaHandlers | ||
, agent | ||
, transaction | ||
, active | ||
, json | ||
; | ||
before(function (done) { | ||
agent = helper.loadMockedAgent(); | ||
before(function (done) { | ||
agent = helper.loadMockedAgent(); | ||
/** | ||
* Mocha is extremely zealous about trapping errors, and runs each test | ||
* in a try / catch block. To get the exception to propagate out to the | ||
* domain's uncaughtException handler, we need to put the test in an | ||
* asynchronous context and break out of the mocha jail. | ||
*/ | ||
process.nextTick(function () { | ||
// disable mocha's error handler | ||
mochaHandlers = helper.onlyDomains(); | ||
/** | ||
* Mocha is extremely zealous about trapping errors, and runs each test | ||
* in a try / catch block. To get the exception to propagate out to the | ||
* domain's uncaughtException handler, we need to put the test in an | ||
* asynchronous context and break out of the mocha jail. | ||
*/ | ||
process.nextTick(function () { | ||
// disable mocha's error handler | ||
mochaHandlers = helper.onlyDomains(); | ||
process.once('uncaughtException', function () { | ||
json = agent.errors.errors[0]; | ||
process.once('uncaughtException', function () { | ||
json = agent.errors.errors[0]; | ||
return done(); | ||
}); | ||
return done(); | ||
}); | ||
var disruptor = agent.tracer.transactionProxy(function () { | ||
transaction = agent.getTransaction(); | ||
domain = transaction.trace.domain; | ||
active = process.domain; | ||
var disruptor = agent.tracer.transactionProxy(function () { | ||
transaction = agent.getTransaction(); | ||
active = process.domain; | ||
// trigger the domain | ||
throw new Error('sample error'); | ||
}); | ||
// trigger the error handler | ||
throw new Error('sample error'); | ||
}); | ||
disruptor(); | ||
}); | ||
disruptor(); | ||
}); | ||
}); | ||
after(function () { | ||
// ...but be sure to re-enable mocha's error handler | ||
transaction.end(); | ||
helper.unloadAgent(agent); | ||
process._events['uncaughtException'] = mochaHandlers; | ||
after(function () { | ||
// ...but be sure to re-enable mocha's error handler | ||
transaction.end(); | ||
helper.unloadAgent(agent); | ||
process._events['uncaughtException'] = mochaHandlers; | ||
}); | ||
it("should not have a domain active", function () { | ||
should.not.exist(active); | ||
}); | ||
it("should find a single error", function () { | ||
expect(agent.errors.errors.length).equal(1); | ||
}); | ||
describe("and an error is traced", function () { | ||
it("should find the error", function () { | ||
should.exist(json); | ||
}); | ||
it("should bind domain to trace", function () { | ||
should.exist(domain); | ||
it("should have 5 elements in the trace", function () { | ||
expect(json.length).equal(5); | ||
}); | ||
it("should have a domain active", function () { | ||
should.exist(active); | ||
it("should always have a 0 (ignored) timestamp", function () { | ||
expect(json[0]).equal(0); | ||
}); | ||
it("the error-handling domain should be the active domain", function () { | ||
expect(domain).equal(active); | ||
it("should have the default name", function () { | ||
expect(json[1]).equal('WebTransaction/Uri/*'); | ||
}); | ||
it("should find a single error", function () { | ||
expect(agent.errors.errors.length).equal(1); | ||
it("should have the error's message", function () { | ||
expect(json[2]).equal('sample error'); | ||
}); | ||
describe("and an error is traced", function () { | ||
it("should find the error", function () { | ||
should.exist(json); | ||
}); | ||
it("should have the error's constructor name (type)", function () { | ||
expect(json[3]).equal('Error'); | ||
}); | ||
it("should have 5 elements in the trace", function () { | ||
expect(json.length).equal(5); | ||
}); | ||
it("should default to passing the stack trace as a parameter", function () { | ||
var params = json[4]; | ||
it("should always have a 0 (ignored) timestamp", function () { | ||
expect(json[0]).equal(0); | ||
}); | ||
it("should have the default name", function () { | ||
expect(json[1]).equal('WebTransaction/Uri/*'); | ||
}); | ||
it("should have the error's message", function () { | ||
expect(json[2]).equal('sample error'); | ||
}); | ||
it("should have the error's constructor name (type)", function () { | ||
expect(json[3]).equal('Error'); | ||
}); | ||
it("should default to passing the stack trace as a parameter", function () { | ||
var params = json[4]; | ||
should.exist(params); | ||
expect(Object.keys(params).length).equal(1); | ||
should.exist(params.stack_trace); | ||
expect(params.stack_trace[0]).equal("Error: sample error"); | ||
}); | ||
should.exist(params); | ||
expect(Object.keys(params).length).equal(1); | ||
should.exist(params.stack_trace); | ||
expect(params.stack_trace[0]).equal("Error: sample error"); | ||
}); | ||
}); | ||
} | ||
}); | ||
}); |
@@ -46,2 +46,24 @@ 'use strict'; | ||
describe("after loading", function () { | ||
var agent; | ||
before(function () { | ||
agent = helper.instrumentMockedAgent(); | ||
}); | ||
after(function () { | ||
helper.unloadAgent(agent); | ||
}); | ||
it("shouldn't have changed createServer's declared parameter names", function (){ | ||
var http = require('http'); | ||
var fn = http.createServer; | ||
/* Taken from | ||
* https://github.com/dhughes/CoolBeans/blob/master/lib/CoolBeans.js#L199 | ||
*/ | ||
var params = fn.toString().match(/function\s+\w*\s*\((.*?)\)/)[1].split(/\s*,\s*/); | ||
expect(params).eql(['requestListener']); | ||
}); | ||
}); | ||
describe("with outbound request mocked", function () { | ||
@@ -48,0 +70,0 @@ var agent |
@@ -148,3 +148,3 @@ 'use strict'; | ||
function (t) { | ||
t.plan(4); | ||
t.plan(3); | ||
@@ -154,5 +154,4 @@ var agent = helper.instrumentMockedAgent(); | ||
function handleRequest(req, res) { | ||
t.ok(process.domain, "should have a domain available"); | ||
process.once('uncaughtException', function (error) { | ||
t.ok(error, "Got error in domain handler."); | ||
t.ok(error, "got error in uncaughtException handler."); | ||
res.statusCode = 501; | ||
@@ -184,3 +183,3 @@ | ||
var server = http.createServer(handleRequest.bind(this)); | ||
var server = http.createServer(handleRequest); | ||
@@ -192,5 +191,3 @@ this.tearDown(function () { | ||
server.listen(1337, function () { | ||
process.nextTick(makeRequest); | ||
}); | ||
server.listen(1337, makeRequest); | ||
}); | ||
@@ -197,0 +194,0 @@ |
@@ -31,3 +31,3 @@ 'use strict'; | ||
test("a. synchronous handler", function (t) { | ||
t.plan(8); | ||
t.plan(7); | ||
@@ -38,3 +38,3 @@ var tracer = new Tracer(agent); | ||
var handler = function (multiplier, multiplicand) { | ||
transaction = tracer.getState().getTransaction(); | ||
transaction = tracer.getTransaction(); | ||
t.ok(transaction, "should find transaction in handler"); | ||
@@ -49,5 +49,4 @@ | ||
t.ok(transaction.state, "state should be attached to transaction"); | ||
var describer = transaction.state.describer; | ||
t.ok(describer, "describer should be on state"); | ||
var describer = transaction.describer; | ||
t.ok(describer, "describer should be on transaction"); | ||
var creations = [ | ||
@@ -99,3 +98,3 @@ '+T', '+S', '+C' // handler invocation | ||
var handler = function (multiplier) { | ||
transaction = tracer.getState().getTransaction(); | ||
transaction = tracer.getTransaction(); | ||
@@ -114,3 +113,3 @@ var callback = function (multiplicand) { | ||
var describer = transaction.state.describer; | ||
var describer = transaction.describer; | ||
var creations = [ | ||
@@ -185,3 +184,3 @@ '+T', '+S', '+C', // handler invocation | ||
var handler = function (multiplier) { | ||
transactions.push(tracer.getState().getTransaction()); | ||
transactions.push(tracer.getTransaction()); | ||
@@ -206,3 +205,3 @@ var callback = function (multiplicand) { | ||
transactions.forEach(function (transaction, index) { | ||
var describer = transaction.state.describer; | ||
var describer = transaction.describer; | ||
var creations = [ | ||
@@ -275,3 +274,3 @@ '+T', '+S', '+C', // handler invocation | ||
var handler = function (multiplier, multiplicand) { | ||
transaction = tracer.getState().getTransaction(); | ||
transaction = tracer.getTransaction(); | ||
var product = multiplier * multiplicand; | ||
@@ -286,3 +285,3 @@ | ||
var describer = transaction.state.describer; | ||
var describer = transaction.describer; | ||
var creations = [ | ||
@@ -364,3 +363,3 @@ '+T', '+S', '+C', // handler invocation | ||
var handler = function (multiplier, multiplicand, callback) { | ||
transaction = tracer.getState().getTransaction(); | ||
transaction = tracer.getTransaction(); | ||
var next = function (value, divisor) { | ||
@@ -379,3 +378,3 @@ return value / divisor; | ||
var describer = transaction.state.describer; | ||
var describer = transaction.describer; | ||
var creations = [ | ||
@@ -497,3 +496,3 @@ '+T', '+S', '+C', // handler invocation | ||
var handler = function (multiplier, multiplicand, callback) { | ||
transactions.push(tracer.getState().getTransaction()); | ||
transactions.push(tracer.getTransaction()); | ||
@@ -519,3 +518,3 @@ var next = function (value, divisor) { | ||
transactions.forEach(function (transaction, index) { | ||
var describer = transaction.state.describer; | ||
var describer = transaction.describer; | ||
var creations = [ | ||
@@ -522,0 +521,0 @@ '+T', '+S', '+C', // 1st handler invocation |
@@ -130,5 +130,6 @@ 'use strict'; | ||
// see shimmer.reinstrument for info on why this is here | ||
shimmer.reinstrument(agent, path.join(__dirname, | ||
'node_modules', 'express', | ||
'node_modules', 'connect')); | ||
shimmer.reinstrument( | ||
agent, | ||
path.join(__dirname, 'node_modules', 'express', 'node_modules', 'connect') | ||
); | ||
@@ -135,0 +136,0 @@ var app = require('express').createServer(); |
@@ -334,11 +334,12 @@ 'use strict'; | ||
"fetched status of othiym23"); | ||
t.equal(children.length, 2, "get has two children"); | ||
t.equal(children.length, 1, "get has two children"); | ||
var view = children[0] || {}; | ||
t.equal(view.name, 'View/room/Rendering', "first child is render of room view"); | ||
t.equal((view.children || {}).length, 0, "view has no children"); | ||
t.equal(view.name, 'View/room/Rendering', "get child is render of room view"); | ||
t.equal((view.children || {}).length, 1, "view has one child"); | ||
children = view.children || []; | ||
var setex = children[1] || {}; | ||
var setex = children[0] || {}; | ||
key = (setex.parameters || {}).key; | ||
t.equal(setex.name, 'Datastore/operation/Redis/setex', "second child is setex"); | ||
t.equal(setex.name, 'Datastore/operation/Redis/setex', "view child is setex"); | ||
t.equal(key, '["sess:' + SESSION_ID + '"]', | ||
@@ -345,0 +346,0 @@ "updated session status"); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
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
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
1450062
357
30670
427
58