In the dynamic landscape of web development, ensuring the optimal performance of your Node.js applications is crucial. One of the key aspects of maintaining a healthy and robust Node.js server is effective monitoring. In this blog post, we will delve into Node server monitoring using powerful tools like Prometheus, Grafana, and Metrics.

Why Monitoring Matters?

Before we dive into the technical details, let's understand why monitoring is essential. Monitoring allows you to:

  1. Detect Performance Issues: Identify and address performance bottlenecks before they impact user experience.
  2. Ensure Stability: Monitor server health and respond proactively to potential issues, reducing downtime.
  3. Capacity Planning: Gain insights into resource usage trends, enabling you to plan for scalability.
  4. Debugging and Troubleshooting: Track and analyze metrics to diagnose and resolve issues quickly.

Components of Node Server Monitoring:

Prometheus is an open-source monitoring and alerting toolkit designed for reliability and scalability. It collects metrics from configured targets at specified intervals, stores them efficiently, and provides a powerful query language for analysis.

Setup sample Node.js project

Create a new directory and setup the Node.js project:

mkdir example-nodejs-app
cd example-nodejs-app
npm init -y

Install prom-client

To export metrics from your Node.js application, we'll use the prom-client library

npm install prom-client

Exposing Metrics

Default Metrics:

Every Prometheus client library automatically provides a set of standard metrics that are considered beneficial for all applications running on a particular platform. The prom-client library also adheres to this practice. These built-in metrics are valuable for tracking resource utilization, such as memory and CPU consumption.

import http from 'http';
import url from 'url';
import { Registry, collectDefaultMetrics } from 'prom-client';

// Create a Registry which registers the metrics
const register = new Registry();

// Add a default label which is added to all metrics
register.setDefaultLabels({
app: 'example-nodejs-app',
});

// Enable the collection of default metrics
collectDefaultMetrics({ register });

// Define the HTTP server
const server = http.createServer(async (req, res) => {

// Retrieve route from request object
const route = url.parse(req.url).pathname;

if (route === '/metrics') {
// Return all metrics the Prometheus exposition format
res.setHeader('Content-Type', register.contentType);
res.end(register.metrics());
}
});
// Start the HTTP server which exposes the metrics on http://localhost:8080/metrics
server.listen(8080);

Default Metrics

Custom Metrics:

Default metrics provide a solid foundation for monitoring, but as your application's complexity grows, you'll need to implement custom metrics to maintain comprehensive oversight.

import http from 'http';
import url from 'url';
import { Registry, collectDefaultMetrics, Histogram } from 'prom-client';

// Create a Registry which registers the metrics
const register = new Registry();

// Add a default label which is added to all metrics
register.setDefaultLabels({
  app: 'example-nodejs-app',
});

// Enable the collection of default metrics
collectDefaultMetrics({ register });

// Create a histogram metric
const httpRequestDurationMicroseconds = new Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in microseconds',
  labelNames: ['method', 'route', 'code'],
  buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10],
});

// Register the histogram
register.registerMetric(httpRequestDurationMicroseconds);

// Define the HTTP server
const server = http.createServer(async (req, res) => {
  // Start the timer
  const end = httpRequestDurationMicroseconds.startTimer();

  // Retrieve route from request object
  const route = url.parse(req.url).pathname;

  if (route === '/metrics') {
    // Return all metrics the Prometheus exposition format
    res.setHeader('Content-Type', register.contentType);
    res.end(register.metrics());
  }

  // End timer and add labels
  end({ route, code: res.statusCode, method: req.method });
});

// Start the HTTP server which exposes the metrics on http://localhost:8080/metrics
server.listen(8080);

Custom Metrics

Copy the above code into your server file and start the Node.js HTTP server. You should now be able to access the metrics via http://localhost:8080/metrics.


Also Read: An Introduction to Basic Prompt Engineering Techniques using ChatGPT


Scrape metrics from Prometheus

Prometheus is available as a Docker image and can be configured using a YAML file. To set up Prometheus, create a configuration file named prometheus.yml with the following contents:

global:
  scrape_interval: 5s

scrape_configs:
  - job_name: "example-nodejs-app"
    static_configs:
      - targets: ["docker.for.mac.host.internal:8080"]

Content of prometheus.yml

This configuration file instructs Prometheus to scrape metrics from all targets every 5 seconds. The targets are specified under the scrape_configs section. On macOS, use docker.for.mac.host.internal as the host to enable the Prometheus Docker container to collect metrics from the local Node.js HTTP server.

To start the Prometheus Docker container and mount the configuration file (prometheus.yml), use the following command:

docker run --rm -p 9090:9090 \
  -v `pwd`/prometheus.yml:/etc/prometheus/prometheus.yml \
  prom/prometheus:v2.20.1

Once the container is running, you should be able to access the Prometheus web UI at http://localhost:9090 .

Grafana

Grafana retrieves metrics from data sources and displays them as charts and graphs. Prometheus is a popular data source for Grafana but can also connect to other monitoring systems. Grafana's alerting feature enables you to set up notifications that will be triggered when specific conditions are met, ensuring that you are promptly informed of any potential issues.

Grafana can also be deployed as a Docker container. To configure Grafana data sources, you can utilize a configuration file named datasources.yml. The file should contain the following content:

apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    orgId: 1
    url: http://docker.for.mac.host.internal:9090
    basicAuth: false
    isDefault: true
    editable: true

content of datasources.yml

The provided configuration file defines Prometheus as a data source for Grafana. When using macOS, you need to specify docker.for.mac.host.internal as the host to enable Grafana to communicate with Prometheus.

To launch a Grafana Docker container and mount the datasources.yml configuration file, execute the following command:

docker run --rm -p 3000:3000 \
  -e GF_AUTH_DISABLE_LOGIN_FORM=true \
  -e GF_AUTH_ANONYMOUS_ENABLED=true \
  -e GF_AUTH_ANONYMOUS_ORG_ROLE=Admin \
  -v `pwd`/datasources.yml:/etc/grafana/provisioning/datasources/datasources.yml \
  grafana/grafana:7.1.5

This command will start a Grafana Docker container, map port 3000 of the container to port 3000 of the host, and disable the login form and enable anonymous access with Admin privileges.

Conclusion

Node server monitoring using Prometheus, Grafana, and Metrics is a powerful and effective way to ensure the optimal performance of your Node.js applications. Prometheus collects metrics from your Node.js application, Grafana visualizes those metrics, and Metrics provides a way to add custom metrics to your application. Together, these tools can help you identify and resolve performance bottlenecks, ensure server stability, and plan for scalability.

Thank you for reading this article. Please consider leaving a comment and subscribing if you liked it.