Diagnostics and Logging
Parcel includes support for rich diagnostics that are used to describe errors and warnings in a format-agnostic way. It also includes a built in logging system that allows Reporter plugins to handle all logs and errors and present them to the user.
Diagnostics
#A Diagnostic
is a JavaScript object with a set of properties that are required to create a useful log message. This can be anything from a verbose message to a warning or error. Diagnostics can include a message, information about the file being processed, a code frame, error information, hints on how to potentially resolve the issue, and a link to documentation to learn more.
The ThrowableDiagnostic
class in the @parcel/diagnostic
package extends the JavaScript Error
object with support for diagnostics. When throwing an error within your plugin, use a ThrowableDiagnostic
object to attach a diagnostic with context about the error. Parcel will automatically attach your plugin name as the origin of the diagnostic.
import ThrowableDiagnostic from '@parcel/diagnostic';
throw new ThrowableDiagnostic({
diagnostic: {
message: 'An error occurred'
}
});
You can also throw multiple diagnostics at once by passing an array to the diagnostic
option of a ThrowableDiagnostic
.
Formatting messages
#To format the messages in a diagnostic, a very minimal version of Markdown is supported. This format has been specifically built to be compatible with terminals and other render targets such as browsers and editors, while also not being too cryptic when displayed without any formatting. @parcel/reporter-cli
uses the @parcel/markdown-ansi
library to convert these Markdown strings to ANSI escape sequences for rendering in a terminal.
The supported Markdown features are **bold**
, *italic*
/_italic_
, __underline__
and ~~strikethrough~~
.
The @parcel/diagnostic
package includes some utilities for working with Markdown messages. The md
tagged template literal handles escaping any interpolated expressions within Markdown strings. This ensures that any special Markdown characters within expressions do not affect the formatting.
import {md} from '@parcel/diagnostic';
throw new ThrowableDiagnostic({
diagnostic: {
message: md`**Error**: Could not parse ${filePath}`
}
});
There are also utilities for formatting interpolated expressions, including md.bold
, md.italic
, md.underline
, and md.strikethrough
.
import {md} from '@parcel/diagnostic';
throw new ThrowableDiagnostic({
diagnostic: {
message: md`**Error**: Could not parse ${md.underline(filePath)}`
}
});
Code frames
#A Diagnostic
can have one or more code frames attached. A code frame includes a file path along with one or more code highlights, which give context about where in a file an error occurred. Code highlights are defined by the line and column position within the file, and may also have a message to be displayed at that position.
Code frames should also include the source code for the file the error occurred in. If omitted, Parcel will read the file from the file system. However, in many cases the input source code may have come from another plugin that ran before, so the code will have been modified in some way. Including the code in the code frame avoids this issue.
throw new ThrowableDiagnostic({
diagnostic: {
message: md`Could not parse ${asset.filePath}`,
codeFrames: [{
filePath: asset.filePath,
code: await asset.getCode(),
codeHighlights: [
{
start: {
line: 1,
column: 5,
},
end: {
line: 2,
column: 3,
},
message: 'Expected a string but got a number'
}
]
}]
}
});
Hints
#Diagnostics can also include hints about how to fix a problem, and a link to documentation for users to learn more. These are provided via the hints
and documentationURL
properties.
throw new ThrowableDiagnostic({
diagnostic: {
message: 'Could not find a config file',
hints: ['Create a tool.config.json file in the project root.'],
documentationURL: 'http://example.com/'
}
});
Logger
#Parcel's logger can be used to log messages in plugins. Every function of a plugin is passed a Logger
instance as a parameter. This instance has all the information Parcel needs to identify your plugin as the origin of the message.
The logger accepts diagnostics, which are JavaScript objects with a standardized set of properties that describe the log message, its origin, and context such as a code frame. Reporter plugins use this information to log your message while having complete freedom over how this data is formatted and displayed.
A Logger
has a function for each log level, including verbose
, info
, log
, warn
and error
. These log levels specify the severity of log messages, which is useful for formatting and filtering. For example, the --log-level
CLI option can be used to choose which messages you want to see. Each logging function also has a single parameter, which can either be a single Diagnostic
object or an array of diagnostics, depending on how many messages you want to log.
Log levels
#Level | When to use | function(s) |
---|---|---|
verbose | Use this when you want to log anything that can be used for debugging issues, while not being particularly interesting for normal usage. | logger.verbose(...) |
info | Use this to log any information that is not related to a problem. | logger.info(...) or logger.log(...) |
warning | Use this to log anything related to a problem that is not critical. | logger.warning(...) |
error | Use this to log any critical issues. You may want to throw a ThrowableDiagnostic instead to cause the build to fail. | logger.error(...) or throw ThrowableDiagnostic(...) |
How to log a message
#Once you're familiar with the Diagnostic
format, you can log anything you want, from verbose messages to errors with code frames and hints. This example shows how to log a warning, complete with a code frame, hints, and a documentation URL.
import {Transformer} from '@parcel/plugin';
export default new Transformer({
async transform({asset, logger}) {
// ...
logger.warn({
message: 'This feature is deprecated.',
codeFrames: [{
filePath: asset.filePath,
code: await asset.getCode(),
codeHighlights: [{
start: {
line: 1,
column: 5
},
end: {
line: 1,
column: 10
}
}]
}],
hints: ['Please use this other feature instead.'],
documentationURL: 'http://example.com/'
});
},
});
Automatically collected logs and errors
#Parcel automatically collects any logs created with console.log
and other console
methods. Whenever console.log
is called, Parcel catches this, converts it to a Diagnostic
object, and sends it to Reporter plugins just like it does with messages sent to the logger
. However, this is not recommended since Parcel does not have as much information as when calling the logger
directly.
Parcel also handles any errors that are thrown within plugins. These are converted into a Diagnostic
, and information about about the plugin is added to it. Errors that are thrown are sent to Reporter plugins, and the build is halted.
API
#PluginLogger parcel/packages/core/logger/src/Logger.js:90
interface PluginLogger {
verbose(diagnostic: DiagnosticWithoutOrigin | Array<DiagnosticWithoutOrigin>): void,
info(diagnostic: DiagnosticWithoutOrigin | Array<DiagnosticWithoutOrigin>): void,
log(diagnostic: DiagnosticWithoutOrigin | Array<DiagnosticWithoutOrigin>): void,
warn(diagnostic: DiagnosticWithoutOrigin | Array<DiagnosticWithoutOrigin>): void,
error(input: Diagnostifiable | DiagnosticWithoutOrigin | Array<DiagnosticWithoutOrigin>): void,
}
Referenced by:
Bundler, Compressor, DedicatedThreadValidator, MultiThreadValidator, Namer, Optimizer, Packager, Reporter, Resolver, Runtime, TransformerDiagnosticHighlightLocation parcel/packages/core/diagnostic/src/diagnostic.js:8
These positions are 1-based (so 1
is the first line/column)
type DiagnosticHighlightLocation = {|
+line: number,
+column: number,
|}
Referenced by:
DiagnosticCodeHighlight, getJSONSourceLocationDiagnosticSeverity parcel/packages/core/diagnostic/src/diagnostic.js:13
Type
type DiagnosticSeverity = 'error' | 'warn' | 'info';
DiagnosticCodeHighlight parcel/packages/core/diagnostic/src/diagnostic.js:19
Note: A tab character is always counted as a single character This is to prevent any mismatch of highlighting across machines
type DiagnosticCodeHighlight = {|
start: DiagnosticHighlightLocation,
Location of the first character that should get highlighted for this highlight.
end: DiagnosticHighlightLocation,
Location of the last character that should get highlighted for this highlight.
message?: string,
A message that should be displayed at this location in the code (optional).
|}
Referenced by:
DiagnosticCodeFrame, generateJSONCodeHighlightsDiagnosticCodeFrame parcel/packages/core/diagnostic/src/diagnostic.js:33
Describes how to format a code frame. A code frame is a visualization of a piece of code with a certain amount of code highlights that point to certain chunk(s) inside the code.
type DiagnosticCodeFrame = {|
code?: string,
The contents of the source file.
If no code is passed, it will be read in from filePath, remember that the asset's current code could be different from the input contents.
filePath?: string,
Path to the file this code frame is about (optional, absolute or relative to the project root)
language?: string,
Language of the file this code frame is about (optional)
codeHighlights: Array<DiagnosticCodeHighlight>,
|}
Referenced by:
DiagnosticDiagnostic parcel/packages/core/diagnostic/src/diagnostic.js:53
A style agnostic way of emitting errors, warnings and info. Reporters are responsible for rendering the message, codeframes, hints, ...
type Diagnostic = {|
message: string,
This is the message you want to log.
origin?: string,
Name of plugin or file that threw this error
stack?: string,
A stacktrace of the error (optional)
name?: string,
Name of the error (optional)
codeFrames?: ?Array<DiagnosticCodeFrame>,
A code frame points to a certain location(s) in the file this diagnostic is linked to (optional)
hints?: Array<string>,
An optional list of strings that suggest ways to resolve this issue
documentationURL?: string,
A URL to documentation to learn more about the diagnostic.
|}
Referenced by:
BuildFailureEvent, DiagnosticLogEvent, DiagnosticWithoutOrigin, Diagnostifiable, ResolveResult, ThrowableDiagnostic, ThrowableDiagnosticOpts, ValidateResult, anyToDiagnostic, errorToDiagnosticPrintableError parcel/packages/core/diagnostic/src/diagnostic.js:78
interface PrintableError extends Error {
fileName?: string,
filePath?: string,
codeFrame?: string,
highlightedCodeFrame?: string,
loc?: ?{
column: number,
line: number,
...
},
source?: string,
}
Referenced by:
Diagnostifiable, errorToDiagnosticDiagnosticWithoutOrigin parcel/packages/core/diagnostic/src/diagnostic.js:91
type DiagnosticWithoutOrigin = {|
...Diagnostic,
origin?: string,
|}
Referenced by:
PluginLoggerDiagnostifiable parcel/packages/core/diagnostic/src/diagnostic.js:97
Something that can be turned into a diagnostic.
Type
type Diagnostifiable = Diagnostic | Array<Diagnostic> | ThrowableDiagnostic | PrintableError | Error | string;
Referenced by:
PluginLogger, anyToDiagnosticanyToDiagnostic parcel/packages/core/diagnostic/src/diagnostic.js:106
Normalize the given value into a diagnostic.
Type
function anyToDiagnostic(input: Diagnostifiable): Array<Diagnostic> {}
errorToDiagnostic parcel/packages/core/diagnostic/src/diagnostic.js:123
Normalize the given error into a diagnostic.
Type
function errorToDiagnostic(error: ThrowableDiagnostic | PrintableError | string, defaultValues?: {|
origin?: ?string,
filePath?: ?string,
|}): Array<Diagnostic> {}
ThrowableDiagnosticOpts parcel/packages/core/diagnostic/src/diagnostic.js:186
type ThrowableDiagnosticOpts = {
diagnostic: Diagnostic | Array<Diagnostic>,
}
Referenced by:
ThrowableDiagnosticThrowableDiagnostic parcel/packages/core/diagnostic/src/diagnostic.js:195
An error wrapper around a diagnostic that can be throw
n (e.g. to signal a build error).
interface ThrowableDiagnostic extends Error {
diagnostics: Array<Diagnostic>,
constructor(opts: ThrowableDiagnosticOpts): void,
}
Referenced by:
Diagnostifiable, errorToDiagnosticgenerateJSONCodeHighlights parcel/packages/core/diagnostic/src/diagnostic.js:222
Turns a list of positions in a JSON file with messages into a list of diagnostics. Uses epoberezkin/json-source-map.
Parameter Descriptions
code
: the JSON codeids
: A list of JSON keypaths (key: "/some/parent/child"
) with corresponding messages,type
signifies whether the key of the value in a JSON object should be highlighted.
Type
function generateJSONCodeHighlights(data: string | {|
data: mixed,
pointers: {|
[key: string]: Mapping
|},
|}, ids: Array<{|
key: string,
type?: ?'key' | 'value',
message?: string,
|}>): Array<DiagnosticCodeHighlight> {}
Referenced by:
encodeJSONKeyComponentgetJSONSourceLocation parcel/packages/core/diagnostic/src/diagnostic.js:247
Converts entries in epoberezkin/json-source-map's result.pointers
array.
Type
function getJSONSourceLocation(pos: Mapping, type?: ?'key' | 'value'): {|
start: DiagnosticHighlightLocation,
end: DiagnosticHighlightLocation,
|} {}
encodeJSONKeyComponent parcel/packages/core/diagnostic/src/diagnostic.js:275
Sanitizes object keys before using them as key
in generateJSONCodeHighlights
Type
function encodeJSONKeyComponent(component: string): string {}
escapeMarkdown parcel/packages/core/diagnostic/src/diagnostic.js:281
Type
function escapeMarkdown(s: string): string {}
TemplateInput parcel/packages/core/diagnostic/src/diagnostic.js:290
Type
type TemplateInput = $FlowFixMe;
Referenced by:
mdmd parcel/packages/core/diagnostic/src/diagnostic.js:293
Type
function md(strings: Array<string>, ...params: Array<TemplateInput>): string {}