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.

Note: The results of Parcel plugins are cached. This means any logs or warnings that a plugin emits will only be shown during a rebuild, and not when cached.

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

#

DiagnosticHighlightLocation 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, getJSONSourceLocation

DiagnosticSeverity 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, generateJSONCodeHighlights

DiagnosticCodeFrame 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:
Diagnostic

Diagnostic 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, errorToDiagnostic

PrintableError 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, errorToDiagnostic

DiagnosticWithoutOrigin parcel/packages/core/diagnostic/src/diagnostic.js:91

type DiagnosticWithoutOrigin = {|
  ...Diagnostic,
  origin?: string,
|}
Referenced by:
PluginLogger

Diagnostifiable 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, anyToDiagnostic

anyToDiagnostic 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:189

type ThrowableDiagnosticOpts = {
  diagnostic: Diagnostic | Array<Diagnostic>,
}
Referenced by:
ThrowableDiagnostic

ThrowableDiagnostic parcel/packages/core/diagnostic/src/diagnostic.js:198

An error wrapper around a diagnostic that can be thrown (e.g. to signal a build error).

interface ThrowableDiagnostic extends Error {
  diagnostics: Array<Diagnostic>,
  constructor(opts: ThrowableDiagnosticOpts): void,
}
Referenced by:
Diagnostifiable, errorToDiagnostic

generateJSONCodeHighlights parcel/packages/core/diagnostic/src/diagnostic.js:225

Turns a list of positions in a JSON5 file with messages into a list of diagnostics. Uses @mischnic/json-sourcemap.

Parameter Descriptions
  • code: the JSON code

  • ids: 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:
encodeJSONKeyComponent

getJSONSourceLocation parcel/packages/core/diagnostic/src/diagnostic.js:251

Converts entries in @mischnic/json-sourcemap's result.pointers array.

Type
function getJSONSourceLocation(pos: Mapping, type?: ?'key' | 'value'): {|
  start: DiagnosticHighlightLocation,
  end: DiagnosticHighlightLocation,
|} {}

encodeJSONKeyComponent parcel/packages/core/diagnostic/src/diagnostic.js:281

Sanitizes object keys before using them as key in generateJSONCodeHighlights

Type
function encodeJSONKeyComponent(component: string): string {}

escapeMarkdown parcel/packages/core/diagnostic/src/diagnostic.js:287

Type
function escapeMarkdown(s: string): string {}

TemplateInput parcel/packages/core/diagnostic/src/diagnostic.js:296

Type
type TemplateInput = $FlowFixMe;
Referenced by:
md

md parcel/packages/core/diagnostic/src/diagnostic.js:299

Type
function md(strings: Array<string>, ...params: Array<TemplateInput>): string {}