Photo by Stephen Phillips - Hostreviews.co.uk on Unsplash
Introduction
Welcome to this hands-on guide where I’ll show you how to monitor external API calls using two popular tools: Node.js with Express and Axios, and Java with Spring Boot. I’ve chosen these tools because they are widely used and very effective for building REST APIs.
In this tutorial, we’ll add Prometheus, a monitoring tool, to help you understand how well your APIs are performing. This can make your applications run smoother and more reliably.
Note: This article is for people who already know a bit about Prometheus. If you’re new to Prometheus or want to learn more, there are lots of great articles and resources available online, especially on Medium.
Let’s get started and see how you can apply these monitoring techniques to your projects.
Monitoring with Node.js(Express) and Axios Interceptors
Setting Up the Project
First, set up a new Node.js project and install necessary packages:
mkdir nodejs-prometheus
cd nodejs-prometheus
npm init -y
npm install express axios prom-client
Implementing Prometheus using Axios Interceptors
We use Axios interceptors to measure the duration of API calls. This allows us to automatically track all outgoing requests made via Axios.
const express = require('express');
const axios = require('axios');
const client = require('prom-client');
const app = express();
const register = new client.Registry();
// Create a Histogram metric to record external API call durations
const externalApiDuration = new client.Histogram({
name: 'external_api_duration_ms',
help: 'Duration of external API calls in ms',
labelNames: ['method', 'endpoint', 'status_code'],
buckets: [50, 100, 200, 300, 400, 500, 1000, 2000, 5000]
});
register.registerMetric(externalApiDuration);
// Set up Axios interceptors
const axiosInstance = axios.create();
axiosInstance.interceptors.request.use(request => {
// Add a custom property to store the start time of the request
request.metadata = { startTime: new Date() };
return request;
});
axiosInstance.interceptors.response.use(
response => {
// Calculate duration and record the metric
const duration = new Date() - response.config.metadata.startTime;
externalApiDuration.observe({
method: response.config.method.toUpperCase(),
endpoint: response.config.url,
status_code: response.status
}, duration);
return response;
},
error => {
// Handle error case
if (error.config && error.config.metadata) {
const duration = new Date() - error.config.metadata.startTime;
externalApiDuration.observe({
method: error.config.method.toUpperCase(),
endpoint: error.config.url,
status_code: error.response ? error.response.status : 500
}, duration);
}
return Promise.reject(error);
}
);
// Example route that makes an external API call using the instrumented Axios instance
app.get('/fetch-data', async (req, res) => {
try {
const response = await axiosInstance.get('https://jsonplaceholder.typicode.com/todos');
res.send(response.data);
} catch (error) {
res.status(500).send('Error fetching data');
}
});
// Expose the metrics endpoint
app.get('/metrics', async (req, res) => {
res.setHeader('Content-Type', register.contentType);
res.send(await register.metrics());
});
app.listen(3000, () => {
console.log('Server is listening on port 3000');
});
Testing and Visualization
Once the application is running, you can access the /metrics endpoint to view your Prometheus metrics. For visualization, Grafana can be connected to your Prometheus server.
Monitoring with Java and Spring Boot
Setting Up the Project
Create a Spring Boot project and include the necessary dependencies for Spring Web, Spring Boot Actuator, and Micrometer Prometheus.
Maven Dependencies
Add the following dependencies to your pom.xml:
<dependencies>
<!-- Spring Boot Starter Web for REST API functionality -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Actuator for exposing metrics -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Micrometer Prometheus for Prometheus metrics -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies>
Configuring the RestTemplate
Create a configuration class to customize RestTemplate with a metrics-interceptor.
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestClientConfig {
@Bean
public RestTemplate restTemplate(MeterRegistry meterRegistry) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add((request, body, execution) -> {
Timer.Sample sample = Timer.start(meterRegistry);
try {
var response = execution.execute(request, body);
sample.stop(meterRegistry.timer("external_api_requests", "method", request.getMethod().toString(),
"uri", request.getURI().toString(), "status", Integer.toString(response.getStatusCode().value())));
return response;
} catch (Exception e) {
sample.stop(meterRegistry.timer("external_api_requests", "method", request.getMethod().toString(),
"uri", request.getURI().toString(), "status", "500"));
throw e;
}
});
return restTemplate;
}
}
Creating a Sample API Endpoint
Implement a controller that uses the configured RestTemplate to perform an external API call.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.client.RestTemplate;
@Controller
public class ApiController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/fetch-data")
public ResponseEntity<String> fetchData() {
String url = "https://jsonplaceholder.typicode.com/todos";
try {
String result = restTemplate.getForObject(url, String.class);
return ResponseEntity.ok(result);
} catch (Exception e) {
return ResponseEntity.status(500).body("Failed to fetch data");
}
}
}
Exposing Metrics with Actuator
Configure your application properties to expose the Prometheus metrics endpoint. Then, use the URL /actuator/prometheus as the target for Prometheus to collect metrics from your application.
management.endpoints.web.exposure.include=health,metrics,info,prometheus
Conclusion
Monitoring external API calls provides critical insights into your application’s interactions with third-party services, enhancing reliability and performance. Implementing such monitoring with Prometheus in Node.js and Java is both strategic and straightforward.