A diagnostic report is a JSON dump of everything Node knows about itself at one instant: JavaScript and native stacks, heap and resource usage, component versions, every open libuv handle with its address and endpoint, the environment and the loaded shared objects. It is the right artifact to attach to a bug report from a machine you cannot log into.
import { readFile } from 'node:fs/promises';
import http from 'node:http';
const server = http.createServer(() => {}).listen(0);
const r = JSON.parse(await readFile(process.report.writeReport(), 'utf8'));
const mb = (b) => (b / 1e6).toFixed(1) + ' MB';
console.log(r.header.event, r.header.nodejsVersion, r.header.platform,
mb(r.resourceUsage.rss));
for (const h of r.libuv.filter((x) => x.is_referenced))
console.log('handle', h.type, h.localEndpoint?.port ?? '');
server.close();Writing Node.js report to file: report.20260917.200643.42088.0.001.json Node.js report completed JavaScript API v25.8.0 win32 58.7 MB handle idle handle tcp 53743
The libuv array is the part worth knowing: a process that refuses to exit is holding a referenced handle, and the report names it. Set --report-uncaught-exception, --report-on-fatalerror and --report-on-signal in production, with --report-dir pointing where your log shipper collects; process.report.directory and .reportOnUncaughtException set the same at runtime.
Tracing answers a different question — not "what is true now" but "what happened, in order". node --trace-event-categories node.async_hooks,node.fs.sync,v8 app.mjs records timestamped begin and end events into node_trace.1.log in Chrome 1 's trace format. The cart script from Inspector and DevTools produced 126 of them that way, 48 being fs.sync.lstat from module resolution and 39 promise lifecycle events. Load the file into chrome://tracing or Perfetto 440,880 (https://ui.perfetto.dev 440,880 ) for a timeline; other categories include node.perf, node.dns.native and v8.gc.