Skip to content

Commit

Permalink
Merge pull request #23 from newrelic-experimental/add-nodejs-agent
Browse files Browse the repository at this point in the history
Add nodejs agent
  • Loading branch information
sdewitt-newrelic authored Mar 24, 2023
2 parents 5910fed + aae4151 commit a07bae0
Show file tree
Hide file tree
Showing 25 changed files with 1,367 additions and 573 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2057,6 +2057,33 @@ npm run delete -- --package-name nr-reports-lambda

## Troubleshooting

### Monitoring

The reporting engine can be monitored using
[the New Relic APM agent for Node.js](https://docs.newrelic.com/docs/apm/agents/nodejs-agent/getting-started/introduction-new-relic-nodejs/)
if you are using [the CLI](#using-the-cli), [the CLI image](#using-the-cli-image),
or [the CRON image](#using-the-cron-image); or using
[Serverless monitoring for AWS Lambda](https://docs.newrelic.com/docs/serverless-function-monitoring/aws-lambda-monitoring/get-started/monitoring-aws-lambda-serverless-monitoring/) if you are using [the AWS Lambda function](#using-the-aws-lambda-function)

To enable the APM agent for the CLI, the CLI image, or the CRON image, simply
update the [Node.js agent configuration](https://docs.newrelic.com/docs/apm/agents/nodejs-agent/installation-configuration/nodejs-agent-configuration/)
just as you would for any other application. If you plan to use the
[agent configuration file method](https://docs.newrelic.com/docs/apm/agents/nodejs-agent/installation-configuration/nodejs-agent-configuration/#config_file),
you can find the agent configuration file at [`nr-reports-cli/newrelic.js`](./nr-reports-cli/newrelic.js).
If you plan to use [environment variables](https://docs.newrelic.com/docs/apm/agents/nodejs-agent/installation-configuration/nodejs-agent-configuration/#environment)
(recommended), refer to the appropriate documentation for setting
environment variables for your runtime environment (shell versus local Docker
image versus container service, etc).

To enable serverless monitoring for AWS Lambda, refer to our
[official documentation](https://docs.newrelic.com/docs/serverless-function-monitoring/aws-lambda-monitoring/enable-lambda-monitoring/enable-aws-lambda-monitoring/)
and the [Deploying the AWS Lambda function](#deploying-the-aws-lambda-function)
section of this document.

Once enabled, an APM or Lambda function entity will be created with the name
specified in the agent configuration and the reporting engine performance
metrics, logs, and traces will be collected and associated with the entity.

### `Error: Failed to launch the browser process!`

If you get the error below while running the Docker CLI or CRON image, you
Expand Down
7 changes: 5 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Stuff and things.
- [ ] Specify time period for query reports
- [ ] Community report repository
- [X] Add Pino
- [ ] Add New Relic agent support to CLI
- [X] Add New Relic agent support to CLI
- [ ] Add slack channel channel
- [ ] Push reports to nerdstorage, generic nerdlet to show reports in nerdstorage
- [ ] Rename channels to destinations
Expand All @@ -41,7 +41,10 @@ Stuff and things.
- [ ] Run manifest reports by name
- [ ] Support `multiAccountMode` on `nrql` tag
- [ ] Add stdout as a channel so content can be piped to anything.
Would need to add a way to silence any logging.
Would need to add a way to silence any logging or send to stderr
- [ ] Add support for `otherResult` for faceted NRQL queries.
- [ ] Add New Relic Metrics/Events as a channel
- [ ] Remove dependency on puppeteer from core?

## Things

Expand Down
199 changes: 128 additions & 71 deletions nr-reports-cli/index.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,90 @@
'use strict'

const newrelic = require('newrelic')

const yargs = require('yargs/yargs'),
puppeteer = require('puppeteer'),
{
rootLogger,
setLogLevel,
Engine,
getArgv,
getEnv,
getOption,
toNumber,
strToLower,
DEFAULT_CHANNEL,
} = require('nr-reports-core')

const DEFAULT_DELAY_TIMEOUT_MS = 10000,
logger = rootLogger

function configureLogger(argv) {
const logLevel = strToLower(getEnv('LOG_LEVEL', 'info')),
useVerbose = getOption(argv, 'verbose', null, logLevel === 'verbose'),
useDebug = getOption(argv, 'debug', null, logLevel === 'debug')

if (useDebug) {
setLogLevel(logger, 'trace')
} else if (useVerbose) {
setLogLevel(logger, 'debug')
}
}

function configureOptions() {
return yargs(getArgv())
.usage('Usage: node index.js ([-f manifest-file] | ([-n name -v values-file] [-p template-path] | [-d dashboard-ids] | [-a account-id -q nrql-query]) [-c channel-ids]) [--verbose] [--debug] [--full-chrome])')
.option('n', {
alias: 'template-name',
type: 'string',
describe: 'Render using the template named <name>',
})
.option('v', {
alias: 'values-file',
type: 'string',
describe: 'Render the template with the parameter values defined in the JSON <values-file>',
})
.option('c', {
alias: 'channel-ids',
type: 'string',
describe: 'Send report output files to the channels listed in <channel-ids> (comma delimited)',
})
.option('f', {
alias: 'manifest',
type: 'string',
describe: 'Render all reports defined in the JSON <manifest-file>',
})
.option('d', {
alias: 'dashboard-ids',
type: 'string',
describe: 'Download dashboard snapshots for all dashboard GUIDs listed in <dashboard-ids> (comma delimited)',
})
.option('a', {
alias: 'account-id',
type: 'string',
describe: 'Account ID to use with <nrql-query>',
})
.option('q', {
alias: 'nrql-query',
type: 'string',
describe: 'Export results of <nrql-query> as a CSV',
})
.option('p', {
alias: 'template-path',
type: 'string',
describe: 'Include all paths in <template-path> on the template path (OS separator delimited)',
default: 'reports',
})
.boolean('verbose')
.describe('verbose', 'Enable verbose mode')
.boolean('debug')
.describe('debug', 'Enable debug mode (be very verbose)')
.boolean('full-chrome')
.default('full-chrome', false)
.describe('full-chrome', 'Don\'t launch Chromium in headless mode (useful for testing templates)')
}

function getApiKey() {
// eslint-disable-next-line dot-notation
const apiKey = getEnv('NEW_RELIC_API_KEY')

if (!apiKey) {
Expand All @@ -22,77 +94,29 @@ function getApiKey() {
return apiKey
}

async function main() {
const logger = rootLogger,
yarggles = yargs(getArgv())
.usage('Usage: node index.js ([-f manifest-file] | ([-n name -v values-file] [-p template-path] | [-d dashboard-ids] | [-a account-id -q nrql-query]) [-c channel-ids]) [--verbose] [--debug] [--full-chrome])')
.option('n', {
alias: 'template-name',
type: 'string',
describe: 'Render using the template named <name>',
})
.option('v', {
alias: 'values-file',
type: 'string',
describe: 'Render the template with the parameter values defined in the JSON <values-file>',
})
.option('c', {
alias: 'channel-ids',
type: 'string',
describe: 'Send report output files to the channels listed in <channel-ids> (comma delimited)',
})
.option('f', {
alias: 'manifest',
type: 'string',
describe: 'Render all reports defined in the JSON <manifest-file>',
})
.option('d', {
alias: 'dashboard-ids',
type: 'string',
describe: 'Download dashboard snapshots for all dashboard GUIDs listed in <dashboard-ids> (comma delimited)',
})
.option('a', {
alias: 'account-id',
type: 'string',
describe: 'Account ID to use with <nrql-query>',
})
.option('q', {
alias: 'nrql-query',
type: 'string',
describe: 'Export results of <nrql-query> as a CSV',
})
.option('p', {
alias: 'template-path',
type: 'string',
describe: 'Include all paths in <template-path> on the template path (OS separator delimited)',
default: 'reports',
})
.boolean('verbose')
.default('verbose', false)
.describe('verbose', 'Enable verbose mode')
.boolean('debug')
.default('debug', false)
.describe('debug', 'Enable debug mode (be very verbose)')
.boolean('full-chrome')
.default('full-chrome', false)
.describe('full-chrome', 'Don\'t launch Chromium in headless mode (useful for testing templates)'),
argv = yarggles.argv,
fullChrome = argv.fullChrome,
logLevel = getEnv('LOG_LEVEL', 'info'),
useVerbose = getOption(argv, 'verbose', null, logLevel === 'verbose'),
useDebug = getOption(argv, 'debug', null, logLevel === 'debug')
function processPendingData() {
const exitDelay = toNumber(
getEnv('DELAY_TIMEOUT_MS', DEFAULT_DELAY_TIMEOUT_MS),
)

if (useDebug) {
rootLogger.level = 'trace'
} else if (useVerbose) {
rootLogger.level = 'debug'
}
logger.debug('Letting agent data settle...')
setTimeout(() => {
logger.debug('Processing pending New Relic data...')
newrelic.shutdown({ collectPendingData: true }, () => {
logger.debug('All done.')

// Following Suggested by New Relic
// eslint-disable-next-line node/no-process-exit
process.exit()
})
}, exitDelay)
}

async function main() {
const argv = configureOptions().argv,
fullChrome = argv.fullChrome

rootLogger.debug(log => {
log(`CLI args: ${process.argv.slice(2).join(' ')}`)
log('Yargs argv:')
log(argv)
})
configureLogger(argv)

try {
const engine = new Engine(
Expand Down Expand Up @@ -129,10 +153,43 @@ async function main() {
}

await engine.run(values)

logger.trace('Recording job status...')

newrelic.recordCustomEvent(
'NrReportsStatus',
{
error: false,
...values.options,
},
)
} catch (err) {
logger.error('Uncaught exception:')
logger.error(err)

newrelic.noticeError(err)

logger.trace('Recording job status...')

newrelic.recordCustomEvent(
'NrReportsStatus',
{
error: true,
message: err.message,
},
)
}
}

main()
// Start a background transaction to track the CLI execution as a Non-Web
// transaction and delay termination after main execution to allow the agent
// data to settle before the agent harvests and sends it.

newrelic.startBackgroundTransaction(
'runReports',
() => (
main().then(() => {
processPendingData()
})
),
)
60 changes: 60 additions & 0 deletions nr-reports-cli/newrelic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* eslint-disable camelcase */
'use strict'

/**
* New Relic agent configuration.
*
* See lib/config/default.js in the agent distribution for a more complete
* description of configuration variables and their potential values.
*/
exports.config = {

/**
* Array of application names.
*/
app_name: ['New Relic Reports CLI'],

/**
* Your New Relic license key.
*/
license_key: '',
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: 'info',
},

/**
* When true, all request headers except for those listed in attributes.exclude
* will be captured for all traces, unless otherwise specified in a destination's
* attributes include/exclude lists.
*/
allow_all_headers: true,
attributes: {

/**
* Prefix of attributes to exclude from all destinations. Allows * as wildcard
* at end.
*
* NOTE: If excluding headers, they must be in camelCase form to be filtered.
*
* @name NEW_RELIC_ATTRIBUTES_EXCLUDE
*/
exclude: [
'request.headers.cookie',
'request.headers.authorization',
'request.headers.proxyAuthorization',
'request.headers.setCookie*',
'request.headers.x*',
'response.headers.cookie',
'response.headers.authorization',
'response.headers.proxyAuthorization',
'response.headers.setCookie*',
'response.headers.x*',
],
},
}
Loading

0 comments on commit a07bae0

Please sign in to comment.