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!
- Install
- Generate a Diagnostic Report
- Redact Secrets From a Report
- Compare Two Reports
- Detect Problems within a Report
- Transforming a Report
- Further Reading
Install
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:
- Recommended: An official package from nodejs.org
- macOS: Homebrew (
brew install node
) - Linux: NodeSource Binary Distributions
- Windows: Chocolatey
- Linux/macOS: A version manager like nvm
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 "process.report.writeReport()"
You’ll see this:
Writing Node.js report to file: report.jsonNode.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:
--experimental-report
; enables diagnostic report functionality--report-filename report.json
; whenever a diagnostic report is written to disk, use this filename--eval "process.report.writeReport()"
; instead of a.js
file, execute the double-quoted string as a script, then exit. The script calls process.report.writeReport(), 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>Commands:rtk diff <file1> <file2> Diff two reportsrtk inspect <file..> Inspect Diagnostic Report file(s) for problemsrtk list-rules Lists built-in rulesrtk redact <file..> Redact secrets from report file(s) and output JSONrtk transform <file..> Transform a reportOptions:--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
:
report-redacted.json{"environmentVariables": {"TERM_SESSION_ID": "[REDACTED]","SSH_AUTH_SOCK": "[REDACTED]","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 "process.report.writeReport()"
You’ll see:
Writing Node.js report to file: report-1.jsonNode.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 "process.report.writeReport()"
And you will see:
Writing Node.js report to file: report-2.jsonNode.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.jsonOp 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:
- 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 seeA
for “added” (when a field is present in the second report and not the first) andD
for “deleted” (when a field is present in the first but not the second). - 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 thecommandLine
prop of the rootheader
prop. - report-1.json: The value at Path in
report-1.json
(if present). - 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:
header
, omitting properties:filename
dumpEventTime
dumpEventTimeStamp
cpus
environmentVariables
userLimits
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 InspectionSeverity File Rule Message──────────────────────────────────────────────────────────────────────INFO report-1.json cpu-usage Mean CPU consumptionpercent (0.47%) iswithin the allowed rangeof 0-50%──────────────────────────────────────────────────────────────────────INFO report-1.json memory-usage Mean memory usagepercent (0.19%) iswithin the allowed range0-50%
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 InspectionSeverity File Rule Message──────────────────────────────────────────────────────────────────────ERROR report-high-cpu-… cpu-usage Mean CPU consumptionpercent (70.12%) isoutside the allowedrange 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 formatfilter
: Pick or omit certain propertiesjson
: 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 secretsstack-hash
: Computes a hash of the exception for metrics; helps answer “have we seen this exception before?”table
: The default transform for thediff
andinspect
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 \report-1.json
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')"
And:
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.
Conclusion
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.