-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Send New Relic events via Butler REST API
Implements #441
- Loading branch information
1 parent
7ff2e94
commit 0da0d85
Showing
4 changed files
with
203 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
const apiPostNewRelicEvent = { | ||
schema: { | ||
summary: 'Post events to New Relic.', | ||
description: 'This endpoint posts events to the New Relic event API.', | ||
body: { | ||
type: 'object', | ||
properties: { | ||
eventType: { | ||
type: 'string', | ||
description: 'Event type. Can be a combination of alphanumeric characters, _ underscores, and : colons.', | ||
example: 'relead-failed', | ||
maxLength: 254, | ||
}, | ||
timestamp: { | ||
type: 'number', | ||
description: | ||
"The event's start time in Unix time. Uses UTC time zone. This field also support seconds, microseconds, and nanoseconds. However, the data will be converted to milliseconds for storage and query. Events reported with a timestamp older than 48 hours ago or newer than 24 hours from the time they are reported are dropped by New Relic. If left empty Butler will use the current time as timestamp.", | ||
example: 1642164296053, | ||
}, | ||
attributes: { | ||
type: 'array', | ||
description: 'Dimensions/attributs that will be associated with the event.', | ||
items: { | ||
type: 'object', | ||
properties: { | ||
name: { | ||
type: 'string', | ||
example: 'host.name', | ||
maxLength: 254, | ||
}, | ||
value: { | ||
type: 'string', | ||
example: 'dev.server.com', | ||
maxLength: 4096, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
required: ['eventType'], | ||
}, | ||
response: { | ||
202: { | ||
description: 'Data accepted and sent to New Relic.', | ||
type: 'object', | ||
properties: { | ||
newRelicResultCode: { type: 'number', example: '202' }, | ||
newRelicResultText: { type: 'string', example: 'Data accepted.' }, | ||
}, | ||
}, | ||
400: { | ||
description: 'Required parameter missing.', | ||
type: 'object', | ||
properties: { | ||
statusCode: { type: 'number' }, | ||
code: { type: 'string' }, | ||
error: { type: 'string' }, | ||
message: { type: 'string' }, | ||
time: { type: 'string' }, | ||
}, | ||
}, | ||
500: { | ||
description: 'Internal error.', | ||
type: 'object', | ||
properties: { | ||
statusCode: { type: 'number' }, | ||
code: { type: 'string' }, | ||
error: { type: 'string' }, | ||
message: { type: 'string' }, | ||
time: { type: 'string' }, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
|
||
module.exports = { | ||
apiPostNewRelicEvent, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
const httpErrors = require('http-errors'); | ||
const axios = require('axios'); | ||
|
||
// Load global variables and functions | ||
const globals = require('../globals'); | ||
const { logRESTCall } = require('../lib/log_rest_call'); | ||
const { apiPostNewRelicEvent } = require('../api/newrelic_event'); | ||
|
||
// eslint-disable-next-line consistent-return | ||
async function handlerPostNewRelicEvent(request, reply) { | ||
try { | ||
logRESTCall(request); | ||
|
||
let payload = []; | ||
const attributes = {}; | ||
const ts = new Date().getTime(); // Timestamp in millisec | ||
|
||
// TODO sanity check parameters in REST call | ||
|
||
// Add static fields to attributes | ||
if (globals.config.has('Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.attribute.static')) { | ||
const staticAttributes = globals.config.get('Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.attribute.static'); | ||
|
||
if (staticAttributes !== null && staticAttributes.length > 0) { | ||
// eslint-disable-next-line no-restricted-syntax | ||
for (const item of staticAttributes) { | ||
attributes[item.name] = item.value; | ||
} | ||
} | ||
} | ||
|
||
// Add attributes passed as parameters | ||
if (request.body.attributes && request.body.attributes.length > 0) { | ||
if (request.body.attributes !== null && typeof request.body.attributes === 'object') { | ||
// eslint-disable-next-line no-restricted-syntax | ||
for (const item of request.body.attributes) { | ||
attributes[item.name] = item.value; | ||
} | ||
} | ||
} | ||
|
||
const tsEvent = request.body.timestamp > 0 ? request.body.timestamp : ts; | ||
|
||
const event = { | ||
timestamp: tsEvent, | ||
eventType: request.body.eventType, | ||
}; | ||
|
||
Object.assign(event, attributes); | ||
|
||
// Build final payload | ||
payload = event; | ||
|
||
globals.logger.debug(`NEWRELIC EVENT: Payload: ${JSON.stringify(payload, null, 2)}`); | ||
|
||
// Preapare call to remote host | ||
|
||
// Build final URL | ||
const remoteUrl = | ||
globals.config.get('Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.url').slice(-1) === '/' | ||
? globals.config.get('Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.url') | ||
: `${globals.config.get('Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.url')}/`; | ||
|
||
const eventApiUrl = `${remoteUrl}v1/accounts/${globals.config.get('Butler.thirdPartyToolsCredentials.newRelic.accountId')}/events`; | ||
|
||
// Add headers | ||
const headers = { | ||
'Content-Type': 'application/json; charset=utf-8', | ||
'Api-Key': globals.config.get('Butler.thirdPartyToolsCredentials.newRelic.insertApiKey'), | ||
}; | ||
|
||
if (globals.config.get('Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.header') !== null) { | ||
// eslint-disable-next-line no-restricted-syntax | ||
for (const header of globals.config.get('Butler.restServerEndpointsConfig.newRelic.postNewRelicEvent.header')) { | ||
headers[header.name] = header.value; | ||
} | ||
} | ||
|
||
// Build body for HTTP POST | ||
const axiosRequest = { | ||
url: eventApiUrl, | ||
method: 'post', | ||
timeout: 5000, | ||
data: event, | ||
headers, | ||
}; | ||
|
||
const res = await axios.request(axiosRequest); | ||
globals.logger.debug(`NEWRELIC EVENT: Result code from posting event to New Relic: ${res.status}, ${res.statusText}`); | ||
|
||
if (res.status === 200) { | ||
// Posting done without error | ||
globals.logger.verbose(`NEWRELIC EVENT: Sent event to New Relic`); | ||
reply.type('text/plain').code(202).send(res.statusText); | ||
// reply.type('application/json; charset=utf-8').code(201).send(JSON.stringify(request.body)); | ||
} else { | ||
reply.send(httpErrors(res.status, `Failed posting event to New Relic: ${res.statusText}`)); | ||
} | ||
|
||
// Required parameter is missing | ||
} catch (err) { | ||
globals.logger.error( | ||
`NEWRELIC EVENT: Failed posting event to New Relic: ${JSON.stringify(request.body, null, 2)}, error is: ${JSON.stringify( | ||
err, | ||
null, | ||
2 | ||
)}` | ||
); | ||
reply.send(httpErrors(500, 'Failed posting event to New Relic')); | ||
} | ||
} | ||
|
||
// eslint-disable-next-line no-unused-vars | ||
module.exports = async (fastify, options) => { | ||
if ( | ||
globals.config.has('Butler.restServerEndpointsEnable.newRelic.postNewRelicEvent') && | ||
globals.config.get('Butler.restServerEndpointsEnable.newRelic.postNewRelicEvent') === true | ||
) { | ||
globals.logger.debug('Registering REST endpoint POST /v4/newrelic/event'); | ||
fastify.post('/v4/newrelic/event', apiPostNewRelicEvent, handlerPostNewRelicEvent); | ||
} | ||
}; |