Comparing version 0.9.7-75 to 0.9.9-82
12
index.js
@@ -15,3 +15,3 @@ 'use strict'; | ||
/** | ||
/* | ||
* Don't set up the rest of the agent if it didn't successfully load its | ||
@@ -21,3 +21,11 @@ * configuration. | ||
if (agent.config) { | ||
// set up all of the instrumentation | ||
/* | ||
* In order to ensure all user code is using instrumented versions of | ||
* modules, instrumentation must be loaded at startup regardless of | ||
* whether or not the agent is enabled in the config. It should be | ||
* possible for users to switch the agent on and off at runtime. | ||
* | ||
* This also requires the agent to be a singleton, or else module loading | ||
* will be patched multiple times, with undefined results. | ||
*/ | ||
shimmer.patchModule(agent); | ||
@@ -24,0 +32,0 @@ shimmer.bootstrapInstrumentation(agent); |
@@ -10,3 +10,3 @@ 'use strict'; | ||
, Context = require(path.join(__dirname, 'context')) | ||
, ErrorService = require(path.join(__dirname, 'error')) | ||
, ErrorTracer = require(path.join(__dirname, 'error')) | ||
, Metrics = require(path.join(__dirname, 'metrics')) | ||
@@ -41,3 +41,8 @@ , MetricNormalizer = require(path.join(__dirname, 'metrics', 'normalizer')) | ||
this.config.on('change', this.updateApdexThreshold.bind(this)); | ||
logger.level(this.config.log_level || 'info'); | ||
/* older versions of the agent configured log level via log_level, but wanted a | ||
* standard stanza to specify logfile location | ||
*/ | ||
logger.level((this.config.logging && this.config.logging.level) || | ||
this.config.log_level || | ||
'info'); | ||
@@ -48,3 +53,3 @@ this.environment = require(path.join(__dirname, 'environment')); | ||
// error tracing | ||
this.errors = new ErrorService(this.config); | ||
this.errors = new ErrorTracer(this.config); | ||
@@ -73,3 +78,5 @@ // metrics | ||
* managed by index.js. An agent will be created even if the agent's disabled by | ||
* the configuration. There's probably a better way to do this. | ||
* the configuration. | ||
* | ||
* @config agent_enabled (true|false) Whether to start up the agent. | ||
*/ | ||
@@ -83,3 +90,3 @@ Agent.prototype.start = function () { | ||
this.harvestIntervalId = setInterval(this.harvest.bind(this), 60 * 1000); | ||
this.harvesterHandle = setInterval(this.harvest.bind(this), 60 * 1000); | ||
sampler.start(this); | ||
@@ -101,3 +108,3 @@ | ||
// stop the harvester coroutine | ||
if (this.harvestIntervalId) clearInterval(this.harvestIntervalId); | ||
if (this.harvesterHandle) clearInterval(this.harvesterHandle); | ||
@@ -104,0 +111,0 @@ // shut down the sampler (and its own coroutines) |
@@ -0,19 +1,116 @@ | ||
/** | ||
* This file includes all of the configuration variables used by the Node.js | ||
* agent. If there's a configurable element of the agent and it's not described | ||
* in here, there's been a terrible mistake. | ||
*/ | ||
exports.config = { | ||
app_name : ['MyApplication'], | ||
host : 'collector.newrelic.com', | ||
port : 80, | ||
log_level : 'info', | ||
agent_enabled : true, | ||
error_collector : { | ||
enabled : true, | ||
/** | ||
* Array of application names. | ||
* | ||
* @env NR_APP_NAME | ||
*/ | ||
app_name : ['MyApplication'], | ||
/** | ||
* The user's license key. Must be set by per-app configuration file. | ||
* | ||
* @env NR_LICENSE_KEY | ||
*/ | ||
license_key : '', | ||
/** | ||
* Hostname for the New Relic collector proxy. | ||
* | ||
* You shouldn't need to change this. | ||
* | ||
* @env NR_COLLECTOR_HOST | ||
*/ | ||
host : 'collector.newrelic.com', | ||
/** | ||
* The port on which the collector proxy will be listening. | ||
* | ||
* You shouldn't need to change this. | ||
* | ||
* @env NR_COLLECTOR_PORT | ||
*/ | ||
port : 80, | ||
logging : { | ||
/** | ||
* Verbosity of the agent logs. The agent uses bunyan | ||
* (https://github.com/trentm/node-bunyan) for its logging, and as such | ||
* the valid logging levels are 'fatal', 'error', 'warn', 'info', 'debug' | ||
* and 'trace'. Logging at levels 'info' and higher is very terse. For | ||
* support requests, attaching logs captured at 'trace' level are extremely | ||
* helpful in chasing down bugs. | ||
* | ||
* @env NR_LOGGING_LEVEL | ||
*/ | ||
level : 'info', | ||
/** | ||
* Where to put the log file -- by default just uses process.cwd + | ||
* 'newrelic_agent.log'. | ||
* | ||
* @env NR_LOGGING_FILEPATH | ||
*/ | ||
filepath : '' | ||
}, | ||
/** | ||
* Whether the agent is enabled. | ||
* | ||
* @env NR_AGENT_ENABLED | ||
*/ | ||
agent_enabled : true, | ||
/** | ||
* Whether to collect & submit error traces to New Relic. | ||
* | ||
* @env NR_ERROR_COLLECTOR_ENABLED | ||
*/ | ||
error_collector : { | ||
enabled : true, | ||
/** | ||
* List of HTTP error status codes the error tracer should disregard. | ||
* Defaults to 404 NOT FOUND. | ||
* | ||
* @env NR_ERROR_COLLECTOR_IGNORE_STATUS_CODES | ||
*/ | ||
ignore_status_codes : [404] | ||
}, | ||
transaction_tracer : { | ||
enabled : true, | ||
/** | ||
* Whether to collect & submit slow transaction traces to New Relic. | ||
* | ||
* @env NR_TRANSACTION_TRACER_ENABLED | ||
*/ | ||
enabled : true, | ||
/** | ||
* The duration at below which the slow transaction tracer should collect a | ||
* transaction trace. If set to 'apdex_f', the threshold will be set to | ||
* 4 * apdex_t, which with a default apdex_t value of 500 milliseconds will | ||
* be 2000 milliseconds. | ||
* | ||
* If a time is provided, it is set in milliseconds. | ||
* | ||
* @env NR_TRANSACTION_TRACER_TRACE_THRESHOLD | ||
*/ | ||
trace_threshold : 'apdex_f' | ||
}, | ||
debug : { | ||
/** | ||
* Whether to enable internal supportability metrics and diagnostics. You're | ||
* welcome to turn these on, but they will probably be most useful to the | ||
* New Relic node engineering team. | ||
*/ | ||
debug : { | ||
/** | ||
* Whether to collect and submit internal supportability metrics alongside | ||
* application performance metrics. | ||
* | ||
* @env NR_DEBUG_INTERNAL_METRICS | ||
*/ | ||
internal_metrics : false, | ||
tracer_tracing : false | ||
/** | ||
* Traces the execution of the transaction tracer. Requires logging.level | ||
* to be set to 'trace' to provide any useful output. | ||
* | ||
* @env NR_DEBUG_TRACER_TRACING | ||
*/ | ||
tracer_tracing : false | ||
} | ||
}; |
@@ -10,45 +10,80 @@ 'use strict'; | ||
var DEFAULT_CONFIG = require(path.join(__dirname, 'config.default')); | ||
/** | ||
* CONSTANTS -- we gotta lotta 'em | ||
*/ | ||
var DEFAULT_CONFIG = require(path.join(__dirname, 'config.default')).config; | ||
function merge(defaults, config) { | ||
Object.keys(defaults).forEach(function (name) { | ||
if (config[name] !== undefined) { | ||
if (Array.isArray(config[name])) { | ||
// use the value in config | ||
} | ||
else if (typeof(defaults[name]) === 'object') { | ||
merge(defaults[name], config[name]); | ||
} | ||
// else use the value in config | ||
} | ||
else { | ||
config[name] = defaults[name]; | ||
} | ||
}); | ||
} | ||
/* | ||
* ENV_MAPPING, LIST_VARS, and BOOLEAN_VARS could probably be unified and | ||
* objectified, but this is simple and works. | ||
*/ | ||
var ENV_MAPPING = { | ||
newrelic_home : "NEWRELIC_HOME", | ||
app_name : "NR_APP_NAME", | ||
license_key : "NR_LICENSE_KEY", | ||
host : "NR_COLLECTOR_HOST", | ||
port : "NR_COLLECTOR_PORT", | ||
logging : { | ||
level : "NR_LOGGING_LEVEL", | ||
filepath : "NR_LOGGING_FILEPATH" | ||
}, | ||
agent_enabled : "NR_AGENT_ENABLED", | ||
error_collector : { | ||
enabled : "NR_ERROR_COLLECTOR_ENABLED", | ||
ignore_status_codes : "NR_ERROR_COLLECTOR_IGNORE_STATUS_CODES" | ||
}, | ||
transaction_tracer : { | ||
enabled : "NR_TRANSACTION_TRACER_ENABLED", | ||
trace_threshold : "NR_TRANSACTION_TRACER_TRACE_THRESHOLD" | ||
}, | ||
debug : { | ||
internal_metrics : "NR_DEBUG_INTERNAL_METRICS", | ||
tracer_tracing : "NR_DEBUG_TRACER_TRACING" | ||
} | ||
}; | ||
function setDefaults(config) { | ||
merge(DEFAULT_CONFIG, config); | ||
} | ||
// values in list variables are comma-delimited lists | ||
var LIST_VARS = [ | ||
"NR_APP_NAME", | ||
"NR_ERROR_COLLECTOR_IGNORE_STATUS_CODES" | ||
]; | ||
function parseVersion() { | ||
var text = fs.readFileSync(path.join(__dirname, '..', 'package.json')); | ||
var json = JSON.parse(text); | ||
/* | ||
* Values in boolean variables. Is pretty tolerant about values, but | ||
* don't get fancy and just use 'true' and 'false', everybody. | ||
*/ | ||
var BOOLEAN_VARS = [ | ||
"NR_AGENT_ENABLED", | ||
"NR_ERROR_COLLECTOR_ENABLED", | ||
"NR_TRANSACTION_TRACER_ENABLED", | ||
"NR_DEBUG_INTERNAL_METRICS", | ||
"NR_DEBUG_TRACER_TRACING" | ||
]; | ||
return json.version; | ||
} | ||
function Config(config) { | ||
EventEmitter.call(this); | ||
for (var name in config) if (config.hasOwnProperty(name)) this[name] = config[name]; | ||
// 1. start by cloning the defaults | ||
var basis = JSON.parse(JSON.stringify(DEFAULT_CONFIG)); | ||
Object.keys(basis).forEach(function (key) { | ||
this[key] = basis[key]; | ||
}.bind(this)); | ||
this.version = parseVersion(); | ||
// 2. override defaults with values from the loaded / passed configuration | ||
this._fromPassed(config); | ||
// 3. override config with environment variables | ||
this._fromEnvironment(); | ||
if (this.debug.internal_metrics) { | ||
this.debug.supportability = new Metrics(this.apdex_t); | ||
} | ||
this.version = require(path.join(__dirname, '..', 'package.json')).version; | ||
} | ||
util.inherits(Config, EventEmitter); | ||
/** | ||
* Accept any configuration passed back from the server. | ||
*/ | ||
Config.prototype.onConnect = function (params) { | ||
@@ -60,2 +95,5 @@ this.apdex_t = params.apdex_t; | ||
/** | ||
* Ensure that the apps names are always returned as a list. | ||
*/ | ||
Config.prototype.applications = function () { | ||
@@ -72,2 +110,83 @@ var apps = this.app_name; | ||
/** | ||
* Safely overwrite defaults with values passed to constructor. | ||
* | ||
* @param object external The configuration being loaded. | ||
* @param object internal Whichever chunk of the config being overrridden. | ||
*/ | ||
Config.prototype._fromPassed = function (external, internal) { | ||
if (!external) return; | ||
if (!internal) internal = this; | ||
Object.keys(external).forEach(function (key) { | ||
var node = external[key]; | ||
if (typeof node === 'object') { | ||
// if it's not in the defaults, it doesn't exist | ||
if (!internal[key]) return; | ||
this._fromPassed(node, internal[key]); | ||
} | ||
else { | ||
internal[key] = node; | ||
} | ||
}.bind(this)); | ||
}; | ||
/** | ||
* Recursively visit the nodes of the constant containing the mapping between | ||
* environment variable names, overriding any configuration values that are | ||
* found from the environment. Operates purely via side effects. | ||
* | ||
* @param object metadata The current level of the mapping object. Should never | ||
* need to set this yourself. | ||
* @param object data The current level of the configuration object. Should | ||
* never need to set this yourself. | ||
*/ | ||
Config.prototype._fromEnvironment = function (metadata, data) { | ||
if (!metadata) metadata = ENV_MAPPING; | ||
if (!data) data = this; | ||
Object.keys(metadata).forEach(function (value) { | ||
var node = metadata[value]; | ||
if (typeof node === 'string') { | ||
var setting = process.env[node]; | ||
if (setting) { | ||
if (LIST_VARS.indexOf(node) > -1) { | ||
data[value] = setting.split(','); | ||
} | ||
else if (BOOLEAN_VARS.indexOf(node) > -1) { | ||
var normalized = setting.toString().toLowerCase(); | ||
switch (normalized) { | ||
case 'false': | ||
case 'f': | ||
case 'no': | ||
case 'n': | ||
case 'disabled': | ||
case '0': | ||
data[value] = false; | ||
break; | ||
default: | ||
data[value] = true; | ||
} | ||
} | ||
else { | ||
data[value] = setting; | ||
} | ||
} | ||
} | ||
else { | ||
// don't crash if the mapping has config keys the current config doesn't. | ||
if (!data[value]) data[value] = {}; | ||
this._fromEnvironment(node, data[value]); | ||
} | ||
}.bind(this)); | ||
}; | ||
/** | ||
* The agent will use the supportability metrics object if it's | ||
* available. | ||
* | ||
* @param string suffix Supportability metric name. | ||
* @param number duration Milliseconds that the measured operation took. | ||
*/ | ||
Config.prototype.measureInternal = function (suffix, duration) { | ||
@@ -81,4 +200,11 @@ if (this.debug.supportability) { | ||
/** | ||
* Create a configuration, either looking in the current working directory | ||
* or in the directory specified by the environment variable NEWRELIC_HOME. | ||
* | ||
* @param object logger A logger following the standard logging API. | ||
* @param object c Optional configuration to be used in place of a config file. | ||
*/ | ||
function initialize(logger, c) { | ||
var nrHome = process.env.NEWRELIC_HOME | ||
var NEWRELIC_HOME = process.env.NEWRELIC_HOME | ||
, DEFAULT_FILENAME = 'newrelic.js' | ||
@@ -88,8 +214,13 @@ , config | ||
if (typeof(c) === 'object') { | ||
if (c && c.config) { | ||
config = c; | ||
} | ||
else { | ||
var filepath = path.join(process.cwd(), DEFAULT_FILENAME); | ||
if (nrHome) filepath = path.join(nrHome, DEFAULT_FILENAME); | ||
var filepath; | ||
if (NEWRELIC_HOME) { | ||
filepath = path.join(NEWRELIC_HOME, DEFAULT_FILENAME); | ||
} | ||
else { | ||
filepath = path.join(process.cwd(), DEFAULT_FILENAME); | ||
} | ||
@@ -102,3 +233,4 @@ try { | ||
"'. A default configuration file can be copied from '" + | ||
path.join(__dirname, 'config.default.js') + "' and renamed to 'newrelic.js' " + | ||
path.join(__dirname, 'config.default.js') + | ||
"' and renamed to 'newrelic.js' " + | ||
"in the directory from which you'll be running your app."); | ||
@@ -110,10 +242,11 @@ } | ||
setDefaults(config); | ||
return new Config(config.config); | ||
} | ||
config = config.config; | ||
if (nrHome) config.newrelic_home = nrHome; | ||
/** | ||
* Preserve the legacy initializer, but also allow consumers to manage their | ||
* own configuration if they choose. | ||
*/ | ||
Config.initialize = initialize; | ||
return new Config(config); | ||
} | ||
exports.initialize = initialize; | ||
module.exports = Config; |
'use strict'; | ||
var path = require('path') | ||
, logger = require(path.join(__dirname, 'logger')).child({component : 'error_service'}) | ||
, logger = require(path.join(__dirname, 'logger')).child({component : 'error_tracer'}) | ||
; | ||
@@ -9,4 +9,16 @@ | ||
/** | ||
* Given either or both of a transaction and an exception, generate an error | ||
* trace in the JSON format expected by the collector. Since this will be | ||
* used by both the HTTP instrumentation, which uses HTTP status codes to | ||
* determine whether a transaction is in error, and the domain-based error | ||
* handler, which traps actual instances of Error, try to set sensible | ||
* defaults for everything. | ||
* | ||
* @param Transaction transaction The agent transaction, presumably coming out | ||
* of the instrumentation. | ||
* @param Error exception Something trapped by an error listener. | ||
*/ | ||
function createError(transaction, exception) { | ||
// the collector throws this out | ||
// the collector throws this out, so don't bother setting it | ||
var timestamp = 0; | ||
@@ -44,10 +56,20 @@ | ||
function ErrorService(config) { | ||
/** | ||
* This is a fairly simple-minded tracer that converts errored-out HTTP | ||
* transactions and JS Errors into the error traces expected by the collector. | ||
* | ||
* It also acts as a collector for the traced errors. | ||
*/ | ||
function ErrorTracer(config) { | ||
this.config = config; | ||
this.errorCount = 0; | ||
this.errors = []; | ||
this.clear(); | ||
} | ||
ErrorService.prototype.clear = function () { | ||
/** | ||
* (Re)Initialize the tracer. | ||
* | ||
* FIXME: for consistency's sake, it would be good to replace the error handler | ||
* between request cycles. | ||
*/ | ||
ErrorTracer.prototype.clear = function () { | ||
this.errorCount = 0; | ||
@@ -57,9 +79,13 @@ this.errors = []; | ||
ErrorService.prototype.ignoreStatusCode = function (code) { | ||
var codes = this.config.error_collector.ignore_status_codes; | ||
ErrorTracer.prototype.ignoreStatusCode = function (code) { | ||
var codes = this.config.error_collector.ignore_status_codes || []; | ||
return codes.indexOf(code) !== -1; | ||
}; | ||
ErrorService.prototype.onTransactionFinished = function (transaction) { | ||
if (!transaction) throw new Error("Error service got a blank transaction."); | ||
/** | ||
* Every finished transaction goes through this handler, so do as | ||
* little as possible. | ||
*/ | ||
ErrorTracer.prototype.onTransactionFinished = function (transaction) { | ||
if (!transaction) throw new Error("Error collector got a blank transaction."); | ||
@@ -70,11 +96,27 @@ var code = transaction.statusCode; | ||
ErrorService.prototype.add = function (transaction, exception) { | ||
ErrorTracer.prototype.add = function (transaction, exception) { | ||
this.errorCount++; | ||
// allow enabling & disabling the error tracer at runtime | ||
if (this.config.error_collector && !this.config.error_collector.enabled) return; | ||
if (exception) logger.trace(exception, "Got exception to trace:"); | ||
var error = createError(transaction, exception); | ||
logger.trace("Adding error: %j", error); | ||
if (this.errors.length < MAX_ERRORS) this.errors.push(error); | ||
if (this.errors.length < MAX_ERRORS) { | ||
logger.debug({error : error}, "Error to be sent to collector:"); | ||
this.errors.push(error); | ||
} | ||
else { | ||
logger.debug("Already have %d errors to send to collector, not logging.", | ||
MAX_ERRORS); | ||
logger.trace({error : error}, "JSON error."); | ||
} | ||
}; | ||
ErrorService.prototype.onSendError = function (errors) { | ||
/** | ||
* If the connection to the collector fails, retain as many as will fit | ||
* without overflowing the current error list. | ||
*/ | ||
ErrorTracer.prototype.onSendError = function (errors) { | ||
var len = Math.min(errors.length, MAX_ERRORS - this.errors.length); | ||
@@ -87,2 +129,2 @@ | ||
module.exports = ErrorService; | ||
module.exports = ErrorTracer; |
@@ -16,2 +16,36 @@ 'use strict'; | ||
/* | ||
* So we can get the logfile location, we need to duplicate some of the | ||
* configurator's logic. | ||
*/ | ||
var NEWRELIC_HOME = process.env.NEWRELIC_HOME | ||
, DEFAULT_FILENAME = 'newrelic.js' | ||
, configpath | ||
, config | ||
; | ||
if (NEWRELIC_HOME) { | ||
configpath = path.join(NEWRELIC_HOME, DEFAULT_FILENAME); | ||
} | ||
else { | ||
configpath = path.join(process.cwd(), DEFAULT_FILENAME); | ||
} | ||
try { | ||
config = require(configpath).config; | ||
} | ||
catch (e) { | ||
console.error("Unable to load New Relic agent configuration to start logger:", | ||
e.stack); | ||
} | ||
var filepath; | ||
// default config is empty string, which is falsy | ||
if (config && config.logging && config.logging.filepath) { | ||
filepath = config.logging.filepath; | ||
} | ||
else { | ||
filepath = path.join(process.cwd(), 'newrelic_agent.log'); | ||
} | ||
var logger = new Logger({ | ||
@@ -22,3 +56,3 @@ name : 'newrelic', | ||
name : 'file', | ||
path : path.join(process.cwd(), 'newrelic_agent.log') | ||
path : filepath | ||
}] | ||
@@ -25,0 +59,0 @@ }); |
@@ -115,2 +115,6 @@ 'use strict'; | ||
// keep instrumented up to date | ||
var pos = instrumented.indexOf(wrapped); | ||
if (pos !== -1) instrumented.splice(pos, 1); | ||
if (!wrapped) return logger.debug("%s not defined, so not unwrapping.", fqmn); | ||
@@ -117,0 +121,0 @@ if (!wrapped.__NR_unwrap) return logger.debug("%s isn't unwrappable.", fqmn); |
@@ -0,8 +1,24 @@ | ||
/** | ||
* New Relic agent configuration. | ||
* | ||
* See lib/config.defaults.js in the agent distribution for a more complete | ||
* description of configuration variables and their potential values. | ||
*/ | ||
exports.config = { | ||
app_name : ['My Application'], | ||
license_key : 'license key here', | ||
log_level : 'trace', | ||
transaction_tracer : { | ||
enabled : true | ||
/** | ||
* Array of application names. | ||
*/ | ||
app_name : ['My Application'], | ||
/** | ||
* Your New Relic license key. | ||
*/ | ||
license_key : 'license key here', | ||
logging : { | ||
/** | ||
* Level at which to log. 'trace' is most useful to New Relic when diagnosing | ||
* issues with the agent, 'info' and higher will impose the least overhead on | ||
* production applications. | ||
*/ | ||
level : 'debug' | ||
} | ||
}; |
@@ -0,1 +1,9 @@ | ||
### v0.9.9-82 / beta-09 (2012-12-12): | ||
* Can now configure the agent via environment variables. See README.md for | ||
details. | ||
* Can now configure the location of the agent log via either logging.filepath | ||
in the configuration file, or NR_LOGGING_FILEPATH in the app's environment. | ||
* Turning off the error tracer via configuration now actually disables it. | ||
### v0.9.7-75 / beta-08 (2012-12-06): | ||
@@ -2,0 +10,0 @@ |
{ | ||
"name": "newrelic", | ||
"version": "0.9.7-75", | ||
"version": "0.9.9-82", | ||
"author": "New Relic Node.js agent team <nodejs@newrelic.com>", | ||
@@ -5,0 +5,0 @@ "contributors": [ |
@@ -24,2 +24,6 @@ # New Relic Node.js agent | ||
If you wish to keep the configuration for the agent separate from your | ||
application, the agent will look for newrelic.js in the directory referenced | ||
by the environment variable `NEWRELIC_HOME` if it's set. | ||
When you start your app, the agent should start up with it and start reporting | ||
@@ -35,2 +39,54 @@ data that will appear within our UI after a few minutes. Because the agent | ||
## Configuring the agent | ||
The agent can be tailored to your app's requirements, both from the server and | ||
via the newrelic.js configuration file you created above. For more details on | ||
what can be configured, refer to `lib/config.default.js`, which documents | ||
the available variables and their default values. | ||
In addition, for those of you running in Heroku, Microsoft Azure or any other | ||
PaaS environment that makes it easier to control configuration via the your | ||
server's environment, all of the configuration variables in newrelic.js have | ||
counterparts that can be set in your service's shell environment. You can | ||
mix and match the configuration file and environment variables freely; the | ||
value found from the environment will always take precedence. | ||
This documentation will be moving to New Relic's servers with the 1.0 release, | ||
but for now, here's a list of the variables and their values: | ||
* `NEWRELIC_HOME`: path to the director in which you've placed newrelic.js. | ||
* `NR_APP_NAME`: The name of this application, for reporting to New Relic's | ||
servers. This value can be also be a comma-delimited list of names. | ||
* `NR_AGENT_ENABLED`: Whether or not the agent should run. Good for | ||
temporarily disabling the agent while debugging other issues with your | ||
code. | ||
* `NR_LICENSE_KEY`: Your New Relic license key. | ||
* `NR_LOGGING_LEVEL`: Logging priority for the New Relic agent. Can be one of | ||
`error`, `warn`, `info`, `debug`, or `trace`. `debug` and `trace` are | ||
pretty chatty; unless you're helping New Relic figure out irregularities | ||
with the agent, you're probably best off using `info` or higher. | ||
* `NR_LOGGING_FILEPATH`: Complete path to the New Relic agent log, including | ||
the filename. The agent will shut down the process if it can't create | ||
this file, and it creates the log file with the same umask of the | ||
process. | ||
* `NR_ERROR_COLLECTOR_ENABLED`: Whether or not to trace errors within your | ||
application. Values are `true` or `false`. | ||
* `NR_ERROR_COLLECTOR_IGNORE_STATUS_CODES`: Comma-delimited list of HTTP | ||
status codes to ignore. Maybe you don't care if payment is required? | ||
* `NR_TRANSACTION_TRACER_ENABLED`: Whether to collect and submit slow | ||
transaction traces to New Relic. Values are `true` or `false`. | ||
* `NR_TRANSACTION_TRACER_TRACE_THRESHOLD`: Millisecond duration at which | ||
a transaction trace will count as slow and be sent to New Relic. Can | ||
also be set to `apdex_f`, at which point it will set the trace threshold | ||
to 4 times the current ApdexT. | ||
* `NR_COLLECTOR_HOST`: Hostname for the New Relic collector proxy. You | ||
shouldn't need to change this. | ||
* `NR_COLLECTOR_PORT`: Port number on which the New Relic collector proxy | ||
will be listening. | ||
* `NR_DEBUG_INTERNAL_METRICS`: Whether to collect internal supportability | ||
metrics for the agent. Don't mess with this unless New Relic asks you to. | ||
* `NR_DEBUG_TRACER_TRACING`: Whether to dump traces of the transaction tracer's | ||
internal operation. You're welcome to enable it, but it's unlikely to be | ||
edifying unless you're a New Relic Node.js engineer. | ||
## Running tests | ||
@@ -37,0 +93,0 @@ |
@@ -13,10 +13,179 @@ 'use strict'; | ||
function idempotentEnv(name, value, callback) { | ||
var is, saved; | ||
// process.env is not a normal object | ||
if (Object.hasOwnProperty.call(process.env, name)) { | ||
is = true; | ||
saved = process.env[name]; | ||
} | ||
process.env[name] = value; | ||
try { | ||
var tc = config.initialize(logger); | ||
callback(tc); | ||
} | ||
catch (error) { | ||
throw error; | ||
} | ||
finally { | ||
if (is) { | ||
process.env[name] = saved; | ||
} | ||
else { | ||
delete process.env[name]; | ||
} | ||
} | ||
} | ||
describe("the agent configuration", function () { | ||
it("should handle a directly passed minimal configuration", function (done) { | ||
it("should handle a directly passed minimal configuration", function () { | ||
var c = config.initialize(logger, {config : {'agent_enabled' : false}}); | ||
c.agent_enabled.should.equal(false); | ||
}); | ||
return done(); | ||
describe("when overriding configuration values via environment variables", function () { | ||
it("should pick up the application name", function () { | ||
idempotentEnv('NR_APP_NAME', 'feeling testy,and schizophrenic', function (tc) { | ||
should.exist(tc.app_name); | ||
expect(tc.app_name).eql(['feeling testy', 'and schizophrenic']); | ||
}); | ||
}); | ||
it("should pick up the license key", function () { | ||
idempotentEnv('NR_LICENSE_KEY', 'hambulance', function (tc) { | ||
should.exist(tc.license_key); | ||
expect(tc.license_key).equal('hambulance'); | ||
}); | ||
}); | ||
it("should pick up the collector host", function () { | ||
idempotentEnv('NR_COLLECTOR_HOST', 'localhost', function (tc) { | ||
should.exist(tc.host); | ||
expect(tc.host).equal('localhost'); | ||
}); | ||
}); | ||
it("should pick up the collector port", function () { | ||
idempotentEnv('NR_COLLECTOR_PORT', 7777, function (tc) { | ||
should.exist(tc.port); | ||
expect(tc.port).equal('7777'); | ||
}); | ||
}); | ||
it("should pick up the log level", function () { | ||
idempotentEnv('NR_LOGGING_LEVEL', 'XXNOEXIST', function (tc) { | ||
should.exist(tc.logging.level); | ||
expect(tc.logging.level).equal('XXNOEXIST'); | ||
}); | ||
}); | ||
it("should pick up the log filepath", function () { | ||
idempotentEnv('NR_LOGGING_FILEPATH', '/highway/to/the/danger/zone', function (tc) { | ||
should.exist(tc.logging.filepath); | ||
expect(tc.logging.filepath).equal('/highway/to/the/danger/zone'); | ||
}); | ||
}); | ||
it("should pick up whether the agent is enabled", function () { | ||
idempotentEnv('NR_AGENT_ENABLED', 0, function (tc) { | ||
should.exist(tc.agent_enabled); | ||
expect(tc.agent_enabled).equal(false); | ||
}); | ||
}); | ||
it("should pick up whether the error collector is enabled", function () { | ||
idempotentEnv('NR_ERROR_COLLECTOR_ENABLED', 'NO', function (tc) { | ||
should.exist(tc.error_collector.enabled); | ||
expect(tc.error_collector.enabled).equal(false); | ||
}); | ||
}); | ||
it("should pick up which status codes are ignored", function () { | ||
idempotentEnv('NR_ERROR_COLLECTOR_IGNORE_STATUS_CODES', '401,404,502', function (tc) { | ||
should.exist(tc.error_collector.ignore_status_codes); | ||
expect(tc.error_collector.ignore_status_codes).eql(['401', '404', '502']); | ||
}); | ||
}); | ||
it("should pick up whether the transaction tracer is enabled", function () { | ||
idempotentEnv('NR_TRANSACTION_TRACER_ENABLED', false, function (tc) { | ||
should.exist(tc.transaction_tracer.enabled); | ||
expect(tc.transaction_tracer.enabled).equal(false); | ||
}); | ||
}); | ||
it("should pick up the transaction trace threshold", function () { | ||
idempotentEnv('NR_TRANSACTION_TRACER_TRACE_THRESHOLD', 0.02, function (tc) { | ||
should.exist(tc.transaction_tracer.trace_threshold); | ||
expect(tc.transaction_tracer.trace_threshold).equal('0.02'); | ||
}); | ||
}); | ||
it("should pick up whether internal metrics are enabled", function () { | ||
idempotentEnv('NR_DEBUG_INTERNAL_METRICS', true, function (tc) { | ||
should.exist(tc.debug.internal_metrics); | ||
expect(tc.debug.internal_metrics).equal(true); | ||
}); | ||
}); | ||
it("should pick up whether tracing of the transaction tracer is enabled", function () { | ||
idempotentEnv('NR_DEBUG_TRACER_TRACING', 'yup', function (tc) { | ||
should.exist(tc.debug.tracer_tracing); | ||
expect(tc.debug.tracer_tracing).equal(true); | ||
}); | ||
}); | ||
}); | ||
describe("with default properties", function () { | ||
var configuration; | ||
before(function () { | ||
configuration = config.initialize(logger, {config : {}}); | ||
// ensure environment is clean | ||
delete configuration.newrelic_home; | ||
}); | ||
it("should have an app name of ['MyApplication']", function () { | ||
configuration.app_name.should.eql(['MyApplication']); | ||
}); | ||
it("should connect to the collector at collector.newrelic.com", function () { | ||
configuration.host.should.equal('collector.newrelic.com'); | ||
}); | ||
it("should connect to the collector on port 80", function () { | ||
configuration.port.should.equal(80); | ||
}); | ||
it("should log at the info level by default", function () { | ||
configuration.logging.level.should.equal('info'); | ||
}); | ||
it("should have a blank default log filepath", function () { | ||
configuration.logging.filepath.should.equal(''); | ||
}); | ||
it("should enable the agent by default", function () { | ||
configuration.agent_enabled.should.equal(true); | ||
}); | ||
it("should enable the error collector by default", function () { | ||
configuration.error_collector.enabled.should.equal(true); | ||
}); | ||
it("should ignore status code 404 by default", function () { | ||
configuration.error_collector.ignore_status_codes.should.eql([404]); | ||
}); | ||
it("should enable the transaction tracer by default", function () { | ||
configuration.transaction_tracer.enabled.should.equal(true); | ||
}); | ||
it("should set the transaction tracer threshold to 'apdex_f' by default", function () { | ||
configuration.transaction_tracer.trace_threshold.should.equal('apdex_f'); | ||
}); | ||
}); | ||
describe("when overriding the config file location via NR_HOME", function () { | ||
@@ -85,19 +254,3 @@ var origHome | ||
}); | ||
it("should correctly expose all of the default properties", function () { | ||
var configuration = config.initialize(logger); | ||
delete configuration.newrelic_home; | ||
configuration.app_name.should.eql(['MyApplication']); | ||
configuration.host.should.equal('collector.newrelic.com'); | ||
configuration.port.should.equal(80); | ||
configuration.log_level.should.equal('info'); | ||
configuration.agent_enabled.should.equal(true); | ||
configuration.error_collector.enabled.should.equal(true); | ||
configuration.error_collector.ignore_status_codes.should.eql([404]); | ||
configuration.transaction_tracer.enabled.should.equal(true); | ||
configuration.transaction_tracer.trace_threshold.should.equal('apdex_f'); | ||
}); | ||
}); | ||
}); |
@@ -12,3 +12,3 @@ 'use strict'; | ||
, DataSender = require(path.join(__dirname, '..', 'lib', 'collector', 'data-sender')) | ||
, ErrorService = require(path.join(__dirname, '..', 'lib', 'error')) | ||
, ErrorTracer = require(path.join(__dirname, '..', 'lib', 'error')) | ||
, Metrics = require(path.join(__dirname, '..', 'lib', 'metrics')) | ||
@@ -100,3 +100,3 @@ , SQLTrace = require(path.join(__dirname, '..', 'lib', 'transaction', 'trace', 'sql')) | ||
it("should send traced errors in the expected format", function () { | ||
var errors = new ErrorService(agent.config); | ||
var errors = new ErrorTracer(agent.config); | ||
@@ -103,0 +103,0 @@ var transaction = new Transaction(agent); |
@@ -10,3 +10,3 @@ 'use strict'; | ||
, dominion = require(path.join(__dirname, '..', 'lib', 'dominion')) | ||
, ErrorService = require(path.join(__dirname, '..', 'lib', 'error')) | ||
, ErrorTracer = require(path.join(__dirname, '..', 'lib', 'error')) | ||
, Transaction = require(path.join(__dirname, '..', 'lib', 'transaction')) | ||
@@ -19,21 +19,36 @@ ; | ||
describe("ErrorService", function () { | ||
describe("ErrorTracer", function () { | ||
var service; | ||
beforeEach(function () { | ||
service = new ErrorService(config.config); | ||
service = new ErrorTracer(config.config); | ||
}); | ||
it("should send the correct number of errors", function () { | ||
var errors = [1, 2, 3, 4, 5]; | ||
it("shouldn't gather errors if it's switched off", function () { | ||
var error = new Error('this error will never be seen'); | ||
service.config.error_collector.enabled = false; | ||
service.onSendError(errors); | ||
expect(service.errorCount).equal(0); | ||
expect(service.errors.length).equal(0); | ||
service.add(error); | ||
expect(service.errorCount).equal(1); | ||
expect(service.errors.length).equal(0); | ||
service.config.error_collector.enabled = true; | ||
}); | ||
it("should retain a maximum of 20 errors to send", function () { | ||
var error = new Error('test error'); | ||
for (var i = 0; i < 5; i++) service.add(null, error); | ||
expect(service.errors.length).equal(5); | ||
service.onSendError(errors); | ||
for (i = 0; i < 5; i++) service.add(null, error); | ||
expect(service.errors.length).equal(10); | ||
service.onSendError(errors); | ||
service.onSendError(errors); | ||
service.onSendError([3,4,5,6,6,6,6,6]); // we're over the max here. | ||
// this will take the tracer 3 over the limit of 20 | ||
for (i = 0; i < 13; i++) service.add(null, error); | ||
expect(service.errorCount).equal(23); | ||
expect(service.errors.length).equal(20); | ||
@@ -68,3 +83,3 @@ }); | ||
beforeEach(function () { | ||
service = new ErrorService(config.config); | ||
service = new ErrorTracer(config.config); | ||
@@ -71,0 +86,0 @@ agent = helper.loadMockedAgent(); |
exports.config = { | ||
app_name : ['My Application'], | ||
license_key : 'license key here', | ||
log_level : 'trace', | ||
logging : { | ||
level : 'trace', | ||
filepath : '../../newrelic_agent.log' | ||
}, | ||
transaction_tracer : { | ||
@@ -6,0 +9,0 @@ enabled : true |
@@ -60,4 +60,4 @@ 'use strict'; | ||
agent.stop(); | ||
shimmer.unpatchModule(); | ||
shimmer.unwrapAll(); | ||
shimmer.unpatchModule(); | ||
}, | ||
@@ -64,0 +64,0 @@ |
25
TODO.md
### KNOWN ISSUES: | ||
* The CPU and memory overhead incurred by the Node agent is relatively | ||
minor (~1-10%, depending on how much of the instrumentation your | ||
apps end up using), but may not be appropriate for production use. | ||
In particular, GC activity is significantly increased due to the | ||
large number of ephemeral objects created by metrics gathering. For | ||
now, be judicious about which production apps you install the agent in. | ||
It may not be appropriate for latency-sensitive or high-throughput | ||
applications. | ||
minor (~1-10%, depending on how much of the instrumentation your | ||
apps end up using), but may not be appropriate for production use. | ||
In particular, GC activity is significantly increased due to the | ||
large number of ephemeral objects created by metrics gathering. For | ||
now, be judicious about which production apps you install the agent in. | ||
It may not be appropriate for latency-sensitive or high-throughput | ||
applications. | ||
* There are irregularities around transaction trace capture and display. | ||
If you notice missing or incorrect information from transaction traces, | ||
let us know. If possible, include the package.json for your application | ||
with your report. | ||
If you notice missing or incorrect information from transaction traces, | ||
let us know. If possible, include the package.json for your application | ||
with your report. | ||
* The agent works only with Node.js 0.6 and newer. | ||
* Transaction and error tracing can't be disabled right now. | ||
* When using Node's included clustering support, each worker process will | ||
open its own connection to New Relic's servers, and will incur its own | ||
overhead costs. | ||
open its own connection to New Relic's servers, and will incur its own | ||
overhead costs. | ||
@@ -21,0 +20,0 @@ ### TO DO: |
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 3 instances 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
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
653520
278
15209
114
31
262