New Relic for Node.js automatically instruments most standard web requests, but sometimes you want expanded instrumentation. With the agent's custom instrumentation API, you can create instrumentation for otherwise unsupported web frameworks, datastores, and message service clients.
The Node.js agent's custom instrumentation API also allows you to:
- Create web transactions (useful for things like web sockets, where transactions can't be automatically created).
- Create non-web background transactions (useful for recording background jobs).
- Target specific sections of your code for deeper analysis.
Agent version requirements
The custom instrumentation methods in this document are available as of Node.js agent version 2.0.0. For information on instrumentation using the custom instrumentation API v1.x, see the documentation for legacy Node.js custom instrumentation.
Instrument unsupported web frameworks
Beginning with Node.js agent version 2.0.0, New Relic provides an API to expand instrumentation for additional web frameworks. For more information, including a tutorial, see the documentation for Node.js web framework instrumentation on GitHub.
Instrument unsupported message service clients
Beginning with Node.js agent version 2.0.0, New Relic provides an API to expand instrumentation for additional message service libraries. For more information, including a tutorial, see the documentation for Node.js message service client instrumentation on GitHub.
Instrument unsupported datastores
Beginning with Node.js agent version 2.0.0, New Relic provides an API to expand instrumentation for additional datastore libraries. For more information, including a tutorial, see the documentation for Node.js datastore instrumentation on GitHub.
Instrument web transactions
In order to create custom web transactions, call startWebTransaction
to start the transaction. To end the transaction, use any of these options:
Ending the transaction | Comments |
---|---|
Promise |
If the handler passed to |
Manual |
If you call If |
Synchronous |
If neither of these options are fulfilled, the transaction will be ended when the handler returns synchronously. |
- Custom web transaction example: Instrument three transactions in socket.io
-
This example instruments a
/websocket/ping
transaction, a/websocket/update
transaction, and a/websocket/new-message
transaction withinsocket.io
. The/ping
example is synchronous, while the/new-message
and/update
examples are asynchronous.var nr = require('newrelic') var app = require('http').createServer() var io = require('socket.io')(app) io.on('connection', function (socket) { socket.on('ping', function (data) { nr.startWebTransaction('/websocket/ping', function transactionHandler() { // Ended automatically after synchronously returning socket.emit('pong') }) }) socket.on('update', function (data) { nr.startWebTransaction('/websocket/update', function transactionHandler() { // Using API#getTransaction var transaction = nr.getTransaction() updateChatWindow(data, function transactionHandler() { socket.emit('update-done') transaction.end() }) }) }) socket.on('new-message', function (data) { nr.startWebTransaction('/websocket/new-message', function transactionHandler() { // Returning a promise return new Promise(function (resolve, reject) { addMessageToChat(data, function () { socket.emit('message-received') resolve() }) }) }) }) })
This method only gives basic timing data for the transaction created. To create more intricate timing data and transaction naming for a particular framework, see the Node.js API documentation and the related tutorial on GitHub.
Instrument background transactions
You can use custom transactions to instrument non-web transactions (background tasks); for example:
- Periodic jobs within your app
- Work that continues after a request completes
To instrument background tasks, call startBackgroundTransaction
in your handler to start a background transaction. To end the transaction, use any of these options:
Ending the transaction | Comments |
---|---|
Promise |
If the handler passed to |
Manual |
If you call If |
Synchronous |
If neither of these options are fulfilled, the transaction will be ended when the handler returns synchronously. |
- Custom background transaction example: Instrument within setInterval
-
This example instruments
update:cache
withinsetInterval
:var nr = require('newrelic') var redis = require('redis').createClient() // Using API#getTransaction to manage ending the transaction setInterval(function () { nr.startBackgroundTransaction('update:cache', function transactionHandler() { var newValue = someDataGenerator() var transaction = nr.getTransaction() redis.set('some:cache:key', newValue, function () { transaction.end() }) }) }, 30000) // Every 30s //Using a promise to manage ending the transaction setInterval(function () { nr.startBackgroundTransaction('flush:cache', function transactionHandler() { return new Promise(function(resolve, reject) { flushCache(redis, function afterFlush(err) { if (err) { return reject(err) } resolve() }) }) }) }, 60*60*1000)
Expand instrumentation within transactions
You can create instrumentation using the instrumentation registration methods on the API. Writing instrumentation using the instrumentation API allows you to specify metrics and naming in greater detail by "monkey patching" methods (replacing functions) on relevant objects. Other options can offer visibility into web transactions that are already instrumented, or gain insight into databases and other in-transaction work that is not automatically instrumented.
To do this, wrap your callbacks in custom tracers. Custom tracers create and collect specific metrics for an additional segment within an existing transaction, such as a particular function or a database call.
- To instrument individual callbacks, call
startSegment()
inside the callback, and move the main callback logic to thehandler
function. - To instrument a function that is called inside an asynchronous function, wrap both the target function and its parent asynchronous function with
startSegment()
.
These examples must be placed in code that is running under a transaction. The origin of the transaction, custom or automatically created, does not matter.
- Example: Instrument a callback
-
This example tracks a single callback:
// Wrap the method in a segment. nr.startSegment('db:createObject', true, function(cb) { // This is recorded as the `db:createObject` segment. db.createObject(cb) }, function(err, result) { // This is recorded as the callback to the `db:createObject` segment. if (util.handleError(err, res)) { return } res.write(JSON.stringify(result.rows[0].id)) res.write('\n') res.end() })
- Example: Instrument asynchronous functions using callbacks
-
This example tracks both
pg.connect
andclient.query
. This is becauseclient.query
is called by an asynchronous parent function (pg.connect
). Otherwise, you would not get any data fromclient.query
. This allowsstartSegment()
to propagate the active transaction across those asynchronous boundaries.nr.startSegment('pg:connect', true, function(cb) { pg.connect(config.db_string, cb) }, function(err, client, done) { if (util.handleError(err, '500', res)) { return done() } nr.startSegment('pg:query', true, function(cb) { client.query('SELECT count(*) FROM test_count'), cb) }, function(err, result) { if (util.handleError(err, '500', res)) { return done() } res.write(result.rows[0].count) res.write('\n') }) })
- Example: Instrument asynchronous functions using promises
-
This example is the same as the callback one, but for interacting with a promise-based API. For promises, simply return the promise and call
then
afterstartSegment
to continue your execution.nr.startSegment('pg:connect', true, function() { // This `pg:connect` segment will time until the returned promise // either resolves or rejects. return pg.connect(config.db_string) }).then(function(client) { // The transaction context is propagated into following promises. return nr.startSegment('pg:query', true, function() { return client.query('SELECT count(*) FROM test_count')) }).then(function(result) { res.write(result.rows[0].count) res.write('\n') res.end() }, function(err) { // Error from querying. util.handleError(err, '500', res) }).finally(function() { return client.release() }) }, function(err) { // Error from connecting. util.handleError(err, '500', res) })
- Example: Instrument
async
functions -
This example shows how to instrument code using
async
/await
to control asynchronous work. This requires using Node 8 or higher, as well as the New Relic for Node.js agent v2.3.0 or higher.try { const client = await nr.startSegment('pg:connect', true, async () => { // Async functions simply return promises, so this example is // very similar to the promise one. return await pg.connect(config.db_string) }) // The transaction context is propagated into the code following `await`. try { const result = await nr.startSegment('pg:query', true, async () => { return await client.query('SELECT count(*) FROM test_count')) }) res.write(result.rows[0].count) res.write('\n') res.end() } catch(err) { // Error from querying. util.handleError(err, '500', res) } finally { await client.release() } } catch(err) { // Error from connecting. util.handleError(err, '500', res) }
- Example: Instrument a synchronous function
-
This example shows how
startSegment
can be used to record a synchronous function that is responsible for assigning its return value to a variable.var result = nr.startSegment('calculateTotal', true, function() { return calculateTotal(outerVar1, outerVar2) })
The
startSegment
function is available of the Node Agent 3.3.0 release.