Skip to main contentreport-toolkit

report-toolkit Quick Start

This “quick start” guide will provide an overview of report-toolkit’s capabilities.

Use the links below to jump to a section, or just install report-toolkit and get moving!


Unsurprisingly, to use report-toolkit, you must install it.

Prerequisite: Node.js v11.8.0 or newer

For the purposes of this guide, you must be running Node.js version v11.8.0 or newer; the Diagnostic Reports API referenced in this guide will reflect its state at version v11.8.0.

Here are some options for installation:

Package Installation

Use your favorite Node.js package manager to install; npm comes packaged with most Node.js distributions.

For this guide, it’s recommended to install report-toolkit globally:

$ npm install report-toolkit --global

Generate a Diagnostic Report

To do much of anything with report-toolkit, you must generate a diagnostic report.

At the time of this writing (2019-09-23), the diagnostic report functionality is “hidden” behind a flag to the node executable; --experimental-report.

The quickest way to generate a report is to evaluate an inline script, like so:

node --experimental-report --report-filename report.json --eval ""

You’ll see this:

Writing Node.js report to file: report.json
Node.js report completed
(node:18881) ExperimentalWarning: report is an experimental feature. This feature could change at any time

Breaking down the arguments to node, we have:

  1. --experimental-report; enables diagnostic report functionality
  2. --report-filename report.json; whenever a diagnostic report is written to disk, use this filename
  3. --eval ""; instead of a .js file, execute the double-quoted string as a script, then exit. The script calls, which writes a report file to disk.

Next, you'll see how report-toolkit enables safe storage and transmission of report files.

Redact Secrets From a Report

Open report.json in your favorite editor (or use cat or less or whathaveyou). Scroll down to—or search for—the environmentVariables property.

environmentVariables is a top-level property of a report file. It contains a complete dump of the environment at the time the report was created. You might notice API keys, cloud provider tokens, credentials, or other session identifiers; in other words, secrets.

Depending on your filesystem permissions, report.json might even be readable by other users who couldn’t otherwise see your environment. This is a potential leak, and we should plug it. report-toolkit to the rescue!

The report-toolkit package provides command-line utility, rtk.

Assuming report-toolkit is installed globally, run:

rtk --help

You should see:

rtk <command>
rtk diff <file1> <file2> Diff two reports
rtk inspect <file..> Inspect Diagnostic Report file(s) for problems
rtk list-rules Lists built-in rules
rtk redact <file..> Redact secrets from report file(s) and output JSON
rtk transform <file..> Transform a report
--color Use color output if possible [boolean] [default: true]
--debug, --verbose Enable debug output [boolean]
--rc Custom file or directory path to .rtkrc.js [string]
--help Show help [boolean]
--version Show version number [boolean]

We see rtk provides commands; the command we want is redact.

By default, the redact command will print its output to STDOUT (the terminal). Instead, let’s use the --output option to write to a new file (smarties may also ask their shell to redirect the output to a file):

rtk redact --output report-redacted.json report.json

Now, open report-redacted.json in your editor (or otherwise display it). Search for the environmentVariables property. Within this object, you will see [REDACTED] wherever report-toolkit found a secret.

Here’s an example excerpt from report-redacted.json:

"environmentVariables": {
"Apple_PubSub_Socket_Render": "[REDACTED]",
"COLORFGBG": "15;0",
"ITERM_PROFILE": "yoyodyne"

If you wish, delete your original report.json; report-redacted.json is now safe to share or send across the wire.

A design goal of report-toolkit is strong security defaults. It will always automatically redact all reports which it ingests. You can disable this via a flag—see the detailed CLI Guide for more information.

Next, you’ll see how rtk can provide a quick comparison of two reports using its diff command.

Compare Two Reports

If you’re having trouble tracking down the difference between two running node processes—say, on two machines that should have identical environments—diagnostic reports and report-toolkit can help.

As you may have deduced, we’ll need two (2) reports to proceed.

Create the First Report

To create the first report, named report-1.json, execute:

node --experimental-report --report-filename report-1.json \
--eval ""

You’ll see:

Writing Node.js report to file: report-1.json
Node.js report completed
(node:18881) ExperimentalWarning: report is an experimental feature. This feature could change at any time

Create the Second Report

To create a second report, repeat the command with the filename changed to report-2.json:

node --experimental-report --report-filename report-2.json \
--eval ""

And you will see:

Writing Node.js report to file: report-2.json
Node.js report completed
(node:18881) ExperimentalWarning: report is an experimental feature. This feature could change at any time

With our two reports in-hand, we can use the diff command to see what’s changed between these two files.

Running a Diff

A “diff” between two reports is not a POSIX diff like you’d see between two versions of the same file. If you want that, you can use the diff utility!

Instead, report-toolkit attempts to disregard typically-irrelevant information, and provide output tailored to the data structure of a report file, which is JSON.

To display a diff, execute:

rtk diff report-1.json report-2.json

You’ll get something like the below (but probably with fancy colors):

[report-toolkit v0.0.0] Diff: report-1.json <=> report-2.json
Op Path report-1.json report-2.json
M header.processId 64217 64166
M header.commandLine[3] report-2.json report-1.json

By default, rtk will display a diff in a tabular format, intended for human consumption.

In the table above, we have four (4) columns. Breaking them down:

  1. Op: This is the type of change. In this case, all changes are modifications, denoted by M. This means that the field exists in both reports, but the value is different. You may also see A for “added” (when a field is present in the second report and not the first) and D for “deleted” (when a field is present in the first but not the second).
  2. Path: This is the JSON “keypath” of the field. If you were to reference the field like the report is a regular JavaScript object, this is how you would do it. [] indicates the presence of an array. header.commandLine[3], for example, 4th element of the commandLine prop of the root header prop.
  3. report-1.json: The value at Path in report-1.json (if present).
  4. report-2.json: The corresponding value at Path in report-2.json (if present).

Note the difference within the header.commandLine array. This reflects the different commands we used to generate each report. We could use that information to determine if the same application generated both reports. Likewise, by comparing header.processId, we could tell if the same process created both reports.

To squelch noise, by default, the diff command shows differences only within these properties:

  1. header, omitting properties:
    1. filename
    2. dumpEventTime
    3. dumpEventTimeStamp
    4. cpus
  2. environmentVariables
  3. userLimits
  4. sharedObjects

Next, we’ll see how report-toolkit can detect problems using its inspect command.

Detect Problems within a Report

A diagnostic report is raw data about a node process. If you’re familiar with diagnostic reports—or happen to know precisely what you’re looking for—you can interpret that data yourself.

But much like a radiologist reading an X-ray, report-toolkit can interpret the raw data in a report and provide akin to a diagnosis—or warn about something you may have otherwise overlooked.

Given a diagnostic report, report-toolkit can run a set of rules (heuristics) against it, providing further information if a “check” fails. This is similar—and, in fact, patterned on—how ESLint runs rules against your codebase. Also akin to ESLint, report-toolkit ships with a set of built-in rules, which we’ll use next.

Let’s take one of the reports we’ve already created (it doesn’t matter which). Execute:

rtk inspect report-1.json

Most likely, this command will result in no output—in other words, success. rtk didn’t find anything worth your attention.

Each message emitted by a rule has an associated severity. These severities, from “most severe” to “least severe”, are:

  • error
  • warning
  • info

The default behavior of rtk is to exclusively show messages with a severity of error.

But you, the user, can control this. Let’s change the severity threshold to info, and see what happens:

rtk inspect report-1.json --severity info

The output should now look something like this (and in color):

[report-toolkit v0.0.0] Diagnostic Report Inspection
Severity File Rule Message
INFO report-1.json cpu-usage Mean CPU consumption
percent (0.47%) is
within the allowed range
of 0-50%
INFO report-1.json memory-usage Mean memory usage
percent (0.19%) is
within the allowed range

Above, we see that two rules (cpu-usage and memory-usage) each output a message. Reading the message, we see that the allowed CPU usage and memory usage, respectively, are within the default thresholds for each rule.

If, for example, the CPU usage was greater than 50%, then instead of a message with severity INFO, the severity would be displayed as ERROR.

Instead of printing a fibonacci sequence in another process, we can use an example “problem” report file to see what an actual error looks like. Download report-high-cpu-usage.example.json; we’ll hand this file to inspect:

rtk inspect report-high-cpu-usage.example.json

Note that the above command does not include --severity info. Now, we should see an error:

[report-toolkit v0.0.0] Diagnostic Report Inspection
Severity File Rule Message
ERROR report-high-cpu-… cpu-usage Mean CPU consumption
percent (70.12%) is
outside the allowed
range of 0-50%

In report-high-cpu-usage.example.json, the reported CPU usage (precisely, the mean across all CPUs) is greater than the default threshold of 50%.

You could use this information to verify that the process isn’t taking up too much CPU—or change the range (via a configuration file) to assert the process remains active—or even verify that utilization is high enough to justify paying for the compute!

Finally, let’s see how rtk’s transform command can help convert a report to a more useful format.

Transforming a Report

The transform command allows you to apply one or more “transformers” to a diagnostic report.

The list of built-in transformers is as follows:

  • csv: Converts to CSV format
  • filter: Pick or omit certain properties
  • json: Converts to JSON format (the default)
  • newline: Converts to newline-delimited format, suitable for piping via shell, consumption via Node.js streams, etc.
  • redact: Redacts secrets
  • stack-hash: Computes a hash of the exception for metrics; helps answer “have we seen this exception before?”
  • table: The default transform for the diff and inspect commands, among others

Since this is supposed to be a quick-start guide, we’ll pick two of these as examples: filter and stack-hash.

The filter Transformer

The filter transformer allows you to essentially “whitelist” or “blacklist” some portion of a report (or both at once; the blacklist takes preference).

For example, if you’d like to retrieve only the version of node used to generate the report, you can use:

rtk transform -t filter --include header.componentVersions.node \

Which will result in something like this (depending on the version of node you used to generate the report):

{"header": {"componentVersions": {"node": "12.10.0"}}}

Likewise, the --exclude argument would allow you to, say, omit the entirety the environmentVariables and sharedObjects properties:

rtk transform -t filter --exclude environmentVariables --exclude \
sharedObjects report-1.json

As you can see from the above, --include and --exclude can be specified multiple times.

For something more practical, let’s try stack-hash.

The stack-hash Transformer

The intent of this transformer is to facilitate the gathering of metrics around exceptions (or more specifically, the stack traces thereof).

node can be configured to automatically generate a diagnostic report when it encounters an uncaught exception or a user signal (e.g., SIGUSR1). We can then use the stack-hash transformer to associate the stack (present in all reports) with a reasonably unique SHA1 hash.

Here’s an example of configuring node to automatically output a report file upon an uncaught exception (and summarily throwing one via --eval):

node --experimental-report --report-filename report-3.json \
--report-uncaught-exception --eval "throw new Error('oh no')"

We can then use the stack-hash transformer on report-3.json:

rtk transform -t stack-hash report-3.json

Which will result in something akin to:

{"dumpEventTime":"2019-10-01T15:26:06Z","filepath":"report-3.json","message":"[eval]:1","sha1":"3f208f53d149120832097cadc0dffe3a584aa91b","stack":["throw new Error('oh no')","^","","Error:oh no","at [eval]:1:7","at Script.runInThisContext (vm.js:126:20)","at Object.runInThisContext (vm.js:316:38)","at Object.<anonymous> ([eval]-wrapper:9:26)","at Module._compile (internal/modules/cjs/loader.js:945:30)","at evalScript (internal/process/execution.js:80:25)"]}

If we repeat the command using a different filename (report-4.json):

node --experimental-report --report-filename report-4.json \
--report-uncaught-exception --eval "throw new Error('oh no')"

And run the transformer:

rtk transform -t stack-hash report-4.json

We’ll get the same hash, even though the dumpEventTime (when the report file was created) and filepath differ:

{"dumpEventTime":"2019-10-01T16:43:50Z","filepath":"report-4.json","message":"[eval]:1","sha1":"3f208f53d149120832097cadc0dffe3a584aa91b","stack":["throw new Error('oh no')","^","","Error:oh no","at [eval]:1:7","at Script.runInThisContext (vm.js:126:20)","at Object.runInThisContext (vm.js:316:38)","at Object.<anonymous> ([eval]-wrapper:9:26)","at Module._compile (internal/modules/cjs/loader.js:945:30)","at evalScript (internal/process/execution.js:80:25)"]}

But not if the message is different:

node --experimental-report --report-filename report-5.json \
--report-uncaught-exception --eval "throw new Error('pizza party')"


rtk transform -t stack-hash report-5.json

Results in:

{"dumpEventTime":"2019-10-01T16:44:09Z","filepath":"report-5.json","message":"[eval]:1","sha1":"61fac641119467714433cea09318274bfd55ed41","stack":["throw new Error('pizza party')","^","","Error: pizza party","at [eval]:1:7","at Script.runInThisContext (vm.js:126:20)","at Object.runInThisContext (vm.js:316:38)","at Object.<anonymous> ([eval]-wrapper:9:26)","at Module._compile (internal/modules/cjs/loader.js:945:30)","at evalScript (internal/process/execution.js:80:25)"]}

You can see that the sha1 property is different, because the exception thrown has a different message.


That wraps up the report-toolkit quick start guide! We’ve learned how to use report-toolkit to:

  • Redact secrets from a diagnostic report
  • Diff two diagnostic reports
  • Check a diagnostic report for potential problems
  • Transform a diagnostic report to another format

See below for links to detailed documentation on all of the above topics.

Further Reading