• Log inFree account

Collect custom business data

10 min

lab

This procedure is part of a lab that teaches you how to monitor your application with New Relic.

Each procedure in the lab builds upon the last, so make sure you've completed the last procedure, Explore your data using NRQL, before starting this one.

With your app instrumented with New Relic agents, you're collecting performance data from your application such as pageview, slow transactions and other. But you also want to collect business metrics from your application. For instance, you want to know what restaurants are popular or which customers spend the most money. To achieve this, you need to collect custom business data from your application.

In this procedure, you instrument your app to collect custom business data and query it.

Record custom attributes

To collect custom data from your application, you need to add custom attributes to your application. You record these attributes with newrelic.addCustomAttribute() on server side and newrelic.addPageAction() on client side.

Here, you record custom attributes to gather details about food orders.

Step 1 of 4

In the terminal window that's running your development server, press <CTRL-C>.

You should see your server shut down. Now you can add some dependencies and update your app logic.

Step 2 of 4

In server/index.js, update the code to add custom attributes on your server side.

var newrelic = require('newrelic');
var express = require('express');
var fs = require('fs');
var open = require('open');
var morgan = require('morgan');
var bodyParser = require('body-parser');
var RestaurantRecord = require('./model').Restaurant;
var MemoryStorage = require('./storage').Memory;
var API_URL = '/api/restaurant';
var API_URL_ID = API_URL + '/:id';
var API_URL_ORDER = '/api/order';
var removeMenuItems = function(restaurant) {
var clone = {};
Object.getOwnPropertyNames(restaurant).forEach(function(key) {
if (key !== 'menuItems') {
clone[key] = restaurant[key];
}
});
return clone;
};
exports.start = function(PORT, STATIC_DIR, DATA_FILE, TEST_DIR) {
var app = express();
var storage = new MemoryStorage();
// log requests
app.use(morgan('combined'))
// serve static files for demo client
app.use(express.static(STATIC_DIR));
// parse body into req.body
app.use(bodyParser.json());
// API
app.get(API_URL, function(req, res, next) {
res.status(200).send(storage.getAll().map(removeMenuItems))
});
app.post(API_URL, function(req, res, next) {
var restaurant = new RestaurantRecord(req.body);
var errors = [];
if (restaurant.validate(errors)) {
storage.add(restaurant);
return res.status(201).send(restaurant);
}
return res.status(400).send({error: errors});
});
app.post(API_URL_ORDER, function(req, res, next) {
console.log(req.body)
var order = req.body;
var itemCount = 0;
var orderTotal = 0;
order.items.forEach(function(item) {
itemCount += item.qty;
orderTotal += item.price * item.qty;
});
newrelic.addCustomAttributes({
'customer': order.deliverTo.name,
'restaurant': order.restaurant.name,
'itemCount': itemCount,
'orderTotal': orderTotal
});
return res.status(201).send({ orderId: Date.now()});
});
app.get(API_URL_ID, function(req, res, next) {
var restaurant = storage.getById(req.params.id);
if (restaurant) {
return res.status(200).send(restaurant);
}
return res.status(400).send({error: 'No restaurant with id "' + req.params.id + '"!'});
});
app.put(API_URL_ID, function(req, res, next) {
var restaurant = storage.getById(req.params.id);
var errors = [];
if (restaurant) {
restaurant.update(req.body);
return res.status(200).send(restaurant);
}
restaurant = new RestaurantRecord(req.body);
if (restaurant.validate(errors)) {
storage.add(restaurant);
return res.status(201).send(restaurant);
}
return res.status(400).send({error: errors});
});
app.delete(API_URL_ID, function(req, res, next) {
if (storage.deleteById(req.params.id)) {
return res.status(204).send(null);
}
return res.status(400).send({error: 'No restaurant with id "' + req.params.id + '"!'});
});
// only for running e2e tests
app.use('/test/', express.static(TEST_DIR));
// start the server
// read the data from json and start the server
fs.readFile(DATA_FILE, function(err, data) {
JSON.parse(data).forEach(function(restaurant) {
storage.add(new RestaurantRecord(restaurant));
});
app.listen(PORT, function() {
open('http://localhost:' + PORT + '/');
console.log('Go to http://localhost:' + PORT + '/');
});
});
// Windows and Node.js before 0.8.9 would crash
// https://github.com/joyent/node/issues/1553
try {
process.on('SIGINT', function() {
// save the storage back to the json file
fs.writeFile(DATA_FILE, JSON.stringify(storage.getAll()), function() {
process.exit(0);
});
});
} catch (e) {}
};
server/index.js

Here, you add the customer name, restaurant name, item count, and order total as custom attributes by calling newrelic.addCustomAttributes(). These custom attributes add metadata to your transaction event. You record them as a part of your transaction event so there is no overhead.

Step 3 of 4

In app/js/services/cart.js, update the code to add PageAction event.

'use strict';
foodMeApp.service('cart', function Cart(localStorage, customer, $rootScope, $http, alert) {
var self = this;
self.add = function(item, restaurant) {
if (!self.restaurant || !self.restaurant.id) {
self.restaurant = {
id: restaurant.id,
name: restaurant.name,
description: restaurant.description
};
}
if (self.restaurant.id == restaurant.id) {
self.items.forEach(function(cartItem) {
if (item && cartItem.name == item.name) {
cartItem.qty ++;
item = null;
}
});
if (item) {
item = angular.copy(item);
item.qty = 1;
self.items.push(item);
}
} else {
alert('Can not mix menu items from different restaurants.');
}
};
self.remove = function(item) {
var index = self.items.indexOf(item);
if (index >= 0) {
self.items.splice(index, 1);
}
if (!self.items.length) {
self.restaurant = {};
}
}
self.total = function() {
return self.items.reduce(function(sum, item) {
return sum + Number(item.price * item.qty);
}, 0);
};
self.submitOrder = function() {
if (self.items.length) {
return $http.post('/api/order', {
items: self.items,
restaurant: self.restaurant,
payment: self.payment,
deliverTo: customer
}).then(function(response) {
self.items.forEach(
function(item){
newrelic.addPageAction('orderItem', {
restaurant: self.restaurant.name,
item: item.name,
qty: item.qty
});
});
self.reset();
return response.data.orderId;
});
}
}
self.reset = function() {
self.items = [];
self.restaurant = {};
};
createPersistentProperty('items', 'fmCartItems', Array);
createPersistentProperty('restaurant', 'fmCartRestaurant', Object);
self.payment = {}; // don't keep CC info in localStorage
function createPersistentProperty(localName, storageName, Type) {
var json = localStorage[storageName];
self[localName] = json ? JSON.parse(json) : new Type;
$rootScope.$watch(
function() { return self[localName]; },
function(value) {
if (value) {
localStorage[storageName] = JSON.stringify(value);
}
},
true);
}
});
app/js/services/cart.js

Here, you added a PageAction that records restaurant name, item, and item quantity whenever a customer places an order.

Step 4 of 4

Now that you've instrumented your application to record custom data, it's time to restart your local server.

bash
$
node ./server/start.js
Go to http://localhost:3000/

Restart your load generator too.

bash
$
python simulator.py

You're now recording custom data from your application. Next, you query this data to get business insights.

lab

This procedure is part of a lab that teaches you how to monitor your application with New Relic. Now that you're collecting custom data, query your custom data.

Copyright © 2022 New Relic Inc.