Skip to content

Commit

Permalink
add events to agent
Browse files Browse the repository at this point in the history
  • Loading branch information
jsumners-nr committed Dec 18, 2024
1 parent 924e36d commit 93ac2fa
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 8 deletions.
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

'use strict'

const HealthReporter = require('./lib/health-reporter')

// Record opening times before loading any other files.
const preAgentTime = process.uptime()
const agentStart = Date.now()
Expand Down Expand Up @@ -156,6 +158,7 @@ function createAgent(config) {
'New Relic requires that you name this application!\n' +
'Set app_name in your newrelic.js or newrelic.cjs file or set environment variable\n' +
'NEW_RELIC_APP_NAME. Not starting!'
agent.healthReporter.setStatus(HealthReporter.STATUS_MISSING_APP_NAME)
throw new Error(message)
}

Expand All @@ -169,6 +172,7 @@ function createAgent(config) {

agent.start(function afterStart(error) {
if (error) {
agent.healthReporter.setStatus(HealthReporter.STATUS_INTERNAL_UNEXPECTED_ERROR)
const errorMessage = 'New Relic for Node.js halted startup due to an error:'
logger.error(error, errorMessage)

Expand Down
8 changes: 6 additions & 2 deletions lib/health-reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ const VALID_CODES = new Map([
['NR-APM-008', 'Agent is disabled via configuration.'],
['NR-APM-009', 'Failed to connect to the New Relic data collector.'],
['NR-APM-010', 'Agent config could not be parsed.'],
['NR-APM-099', 'Agent has shutdown.']
['NR-APM-099', 'Agent has shutdown.'],
// Codes 300 through 399 are reserved for the Node.js Agent.
['NR-APM-300', 'An unexpected error occurred.']
])

function writeStatus({ file, healthy = true, code, msg, startTime, callback } = {}) {
Expand Down Expand Up @@ -65,6 +67,9 @@ class HealthReporter {
static STATUS_CONFIG_PARSE_FAILURE = 'NR-APM-010'
static STATUS_AGENT_SHUTDOWN = 'NR-APM-099'

// STATUS_INTERNAL errors are the Node.js Agent specific error codes.
static STATUS_INTERNAL_UNEXPECTED_ERROR = 'NR-APM-300'

constructor({ logger = defaultLogger, setInterval = global.setInterval } = {}) {
const fleetId = process.env.NEW_RELIC_SUPERAGENT_FLEET_ID
const outDir = process.env.NEW_RELIC_SUPERAGENT_HEALTH_DELIVERY_LOCATION
Expand Down Expand Up @@ -135,7 +140,6 @@ class HealthReporter {
}

if (VALID_CODES.has(status) === false) {
// TODO: if we ever add codes in our reserved block (300-399), account for them here
this.#logger.warn(`invalid health reporter status provided: ${status}`)
return
}
Expand Down
36 changes: 30 additions & 6 deletions test/unit/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@

const test = require('node:test')
const assert = require('node:assert')
const tspl = require('@matteo.collina/tspl')

const sinon = require('sinon')

const HealthReporter = require('../../lib/health-reporter')
const proxyquire = require('proxyquire').noCallThru()
const createLoggerMock = require('./mocks/logger')
const createMockAgent = require('./mocks/agent')
Expand Down Expand Up @@ -322,30 +324,52 @@ test('index tests', async (t) => {
)
})

await t.test('should throw error is app name is not set in config', (t) => {
await t.test('should throw error is app name is not set in config', async (t) => {
const plan = tspl(t, { plan: 3 })
const setStatus = HealthReporter.prototype.setStatus
HealthReporter.prototype.setStatus = (status) => {
plan.equal(status, HealthReporter.STATUS_MISSING_APP_NAME)
}
t.after(() => {
HealthReporter.prototype.setStatus = setStatus
})

t.nr.processVersionStub.satisfies.onCall(0).returns(true)
t.nr.processVersionStub.satisfies.onCall(1).returns(false)
t.nr.mockConfig.applications.returns([])
loadIndex(t)
assert.equal(t.nr.loggerMock.error.callCount, 1, 'should log an error')
assert.match(
plan.equal(t.nr.loggerMock.error.callCount, 1, 'should log an error')
plan.match(
t.nr.loggerMock.error.args[0][0].message,
/New Relic requires that you name this application!/
)

await plan.completed
})

await t.test('should log error if agent startup failed', (t) => {
await t.test('should log error if agent startup failed', async (t) => {
const plan = tspl(t, { plan: 3 })
const setStatus = HealthReporter.prototype.setStatus
HealthReporter.prototype.setStatus = (status) => {
plan.equal(status, HealthReporter.STATUS_INTERNAL_UNEXPECTED_ERROR)
}
t.after(() => {
HealthReporter.prototype.setStatus = setStatus
})

t.nr.processVersionStub.satisfies.onCall(0).returns(true)
t.nr.processVersionStub.satisfies.onCall(1).returns(false)
t.nr.mockConfig.applications.returns(['my-app-name'])
const err = new Error('agent start failed')
t.nr.MockAgent.prototype.start.yields(err)
loadIndex(t)
assert.equal(t.nr.loggerMock.error.callCount, 1, 'should log a startup error')
assert.equal(
plan.equal(t.nr.loggerMock.error.callCount, 1, 'should log a startup error')
plan.equal(
t.nr.loggerMock.error.args[0][1],
'New Relic for Node.js halted startup due to an error:'
)

await plan.completed
})

await t.test('should log warning if not in main thread and make a stub api', (t) => {
Expand Down
3 changes: 3 additions & 0 deletions test/unit/mocks/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@
*/

'use strict'

const { EventEmitter } = require('events')
const util = require('util')
const sinon = require('sinon')
const HealthReporter = require('../../../lib/health-reporter')

module.exports = (sandbox = sinon, metricsMock) => {
function MockAgent(config = {}) {
EventEmitter.call(this)
this.config = config
this.config.app_name = 'Unit Test App'
this.metrics = metricsMock
this.healthReporter = new HealthReporter()
}
MockAgent.prototype.start = sandbox.stub()
MockAgent.prototype.recordSupportability = sandbox.stub()
Expand Down

0 comments on commit 93ac2fa

Please sign in to comment.