The core libraries that come bundled with the Node binary are written in JavaScript, which means we can debug them the same way we debug our own code. This level of introspection means we can understand internal mechanics to a fine level of detail.
Let's use Devtools to pick apart how util.debuglog works.
Devtools
To understand how to use Devtools, see the first recipe in this chapter, Debugging Node with Chrome Devtools.
We'll run our code we prepared in the Getting ready section like so (Node 8+):
$ NODE_DEBUG=timer node --inspect-brk index.js
Or if we're using Node 6.3.0+, use the following:
$ NODE_DEBUG=timer node --debug-brk --inspect index.js
Now if we navigate to chrome://inspect, click the inspect link this will open Devtools for our Node process. We should then see something like the following:
Now in left hand pane (the Navigation pane), we should see two drop-down trees (no domain) and file://. The (no domain) files are files that came compiled into Node.
Let's click the small right-facing triangle next to (no domain) to expand the list. Then locate the util.js file and double-click to open. At this point, we should see something like the following:
Next, we want to find the debuglog function. An easy way to do this is to press Cmd + F on macOS or Ctrl + F on Linux and Windows, to bring up the small find dialog, then type debuglog. This should highlight the exported debuglog method:
If we read the exported function, we should be able to ascertain that given the right conditions (for example, if the flag is set on NODE_DEBUG), a function is created and associated to a namespace. Different Node versions could have differences in their util.js. In our case, the first line of this generated function is line 157, so we set a breakpoint on line 157 (or wherever the first line of the generated function may be):
Now if we press run, our breakpoint should be triggered almost immediately. Let's hover over the arguments object referenced in the generated function:
We should see that the second argument passed to the generated debug function is 100 this relates to the millisecond parameter we pass to setTimeout in our index.js and is part of the first debug message (no 100 list was found...).
Now let's hit the blue play button four more times until it changes to a pause button and the top-right corner shows an error count of 4 as shown in the following screenshot:
Devtools perceives each log message as an error because the debug messages are written to STDERR. This is why the error count in the top-right corner is 4.
Now let's open a new browser tab and navigate to http://localhost:3000.
Devtools should have paused again at our breakpoint. If we hover over the arguments object in the generated function we should see that the second argument is 12000. This relates to the default 2 minute timeout on sockets (as discussed in the main recipe):
If we hit the play button again and inspect the arguments object, we should see the second argument is a number that's less than 1000:
Over on the right-hand side, in the Call Stack panel there's a frame called utcDate. Let's select that frame to view the function:
This function is in a library that's only for internal core use called _http_outgoing.js.
We can see that it's currently within an if block that checks whether dateCache is falsey. If dateCache is falsey, it creates a new Date object and assigns the output of toUTCString to dateCache. Then it uses timers.enroll. This is a way of creating a setTimeout where the provided object represents the timeout reference. It sets the time to 1000 minus the millisecond unit in the date object that effectively measures how long there is left of the current second. Then it calls timers._unrefActive, which activates the timer without allowing the timer to keep the event loop open (which means the fact the queued timer operation won't keep the process alive). The utcDate._onTimeout method sets dateCache to undefined, so at the end of the timeout, dateCache is cleared.
If we look down the Call Stack panel, we should be able to infer that the utcDate function is called when a request is made, and is to do with HTTP header generation (specifically the Date HTTP header).
The net effect is that a process may receive, say, 10,000 requests a second, and only the first of those 10,000 has to perform the relatively expensive Date generation, while the following 9,999 requests all use the cached date.
And that's the sort of thing we can discover by debugging core.