How to SRE-ify your React app with Prometheus
I am not a JavaScript developer. However, I was given a task at work
recently that forced me to enter the abyss and get good at keeping my Promise
s.
I was asked to create a webinar on helping developers become better SREs through observability and instrumentation. The objective was to take a broken web app and add enough monitoring and logging to it to make troubleshooting its brokenness easier. (I’ll update this post with a link when we broadcast it on April 22nd!)
The web app under repair was a React app with a Rails backend. JavaScript time.
While getting Prometheus wired up with the Rails backend was pretty easy, I had a shockingly difficult time getting it working with Node and React. The web app was created with Create React App, which makes it stupidly easy to get started with React with the help of thousands of lines of black magic (no, seriously; look at the codebase if you don’t believe me). While Create React App handles starting the Express web server for you, it doesn’t provide a whole lot in the way of configuring that server.
I spent several hours figuring out how to get React and Prom talking to each other (and finding surprisingly little in instrumenting a React app with Prom). I succeeded! It was WAY easier than I thought.
I hope this blog post saves you hours of pain. Apologies for any JavaScript errors or misgivings; JS isn’t my bag!
Assumptions
I’m going to assume that you have a Prometheus server already configured, so I won’t cover getting started with Prometheus. Read the excellent configuration documentation if you’re interested in learning more.
I’m also going to assume that your app was created with Create React App and is using the
built-in Express server that comes with react-scripts
.
How to Avoid Pain and Suffering
- Add a Prometheus target to
prometheus.yml
for the metrics that you’re about to expose:
---
global:
# rest of the damn owl
- job_name: frontend
scrape_interval: 5s
scrape_timeout: 2s
honor_labels: true
static_configs:
- targets: ['frontend:5000'] # Change to your app's URL
- Restart your Prom server. The target should register and be down.
- In your React app’s repository, add
express-prom-bundle
andprom-client
to yourdependencies
node inpackage.json
and change yourstart
script tonode server.js
:
{
"name": "project_organizer_front_end",
"version": "0.1.0",
"private": true,
"dependencies": {
"prom-client": "12.0.0",
"express-prom-bundle": "6.0.0",
... rest of dependencies
},
"scripts": {
"start": "node server.js", <-- change this
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
-
At the root of your React app’s repository, create a new file called
server.js
:touch server.js
-
In your editor of choice, copy and paste the following:
const express = require('express');
const favicon = require('express-favicon');
const path = require('path');
const port = process.env.PORT || 8080;
const prometheus = require('express-prom-bundle')
// This will create the /metrics endpoint for you and expose Node default
// metrics.
const metricsMiddleware = prometheus({
includeMethod: true,
includePath: true,
promClient: { collectDefaultMetrics: {} }
})
const app = express();
app.use(favicon(__dirname + '/build/favicon.ico'));
// the __dirname is the current directory from where the script is running
app.use(express.static(__dirname));
app.use(express.static(path.join(__dirname, 'build')));
app.use(metricsMiddleware);
app.get('/*', function (req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html')); // <-- change if not using index.html
});
app.listen(port);
-
Restart Node but make sure that you build your app first:
npm build && npm start
-
Go back into Prometheus. Within a few seconds, the target should be up:
- You’re done!