Capture DNS Lookup Timings in Axios
Capturing detailed DNS lookup timings in Axios requires tapping into
Node.js's underlying network socket events. While Axios does not provide
built-in DNS metrics out of the box, you can measure the precise
duration of the domain resolution phase by attaching listeners to the
socket and its lookup event via Axios
interceptors or custom HTTP agents.
Understanding the Network Socket Lifecycle
In Node.js, an outgoing HTTP request follows a distinct connection lifecycle:
- Socket initialization: The request is assigned a socket.
- DNS Lookup (
lookup): The domain name is resolved to an IP address. - TCP Connection (
connect/secureConnect): The TCP (and optional TLS) handshake is established. - Data transfer: The HTTP payload is sent and the response is received.
The DNS lookup duration is the time elapsed between when the socket
begins domain resolution and when the lookup event
fires.
Implementing DNS Timing with Axios Interceptors
The most straightforward way to capture this metric is by using Axios
request and response interceptors to attach event listeners to the
underlying http.ClientRequest instance.
const axios = require('axios');
const axiosInstance = axios.create();
axiosInstance.interceptors.request.use((config) => {
config.metadata = { startTime: process.hrtime.bigint() };
return config;
});
axiosInstance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
return Promise.reject(error);
}
);
// Function to make a request and track DNS timings
async function makeTimedRequest(url) {
const timings = {
dnsLookup: null,
totalTime: null,
};
let dnsStart;
const response = await axiosInstance.get(url, {
transport: {
request: (options, callback) => {
const http = options.protocol === 'https:' ? require('https') : require('http');
const req = http.request(options, callback);
req.on('socket', (socket) => {
dnsStart = process.hrtime.bigint();
socket.on('lookup', (err, address, family, host) => {
const dnsEnd = process.hrtime.bigint();
// Convert nanoseconds to milliseconds
timings.dnsLookup = Number(dnsEnd - dnsStart) / 1e6;
});
});
return req;
},
},
});
return { data: response.data, timings };
}Handling Connection Reuse and Keep-Alive
When keepAlive is enabled in http.Agent or
https.Agent, DNS resolution is skipped for pooled, reused
connections. In such cases, the lookup event will not
fire.
To account for connection pooling:
- Initialize the DNS timing metric to
0ornullto explicitly represent a reused connection. - Monitor
socket.on('connect')or checksocket.reusedSocketto differentiate between a cached DNS connection and an omitted DNS step.
const http = require('http');
const https = require('https');
const httpAgent = new http.Agent({ keepAlive: true });
const httpsAgent = new https.Agent({ keepAlive: true });
const client = axios.create({
httpAgent,
httpsAgent,
});If timings.dnsLookup remains undefined or unset during a
request, it confirms that a persistent socket was reused and no DNS
query was dispatched.