Logging

The way to output messages to your users

Logger

Every time you want to log something in a plugin, you have to go through the logger. Every function of a plugin is passed a logger instance as a parameter. This instance has already all the information Parcel needs to identify your plugin as the origin of the message.

The logger uses a format we called diagnostics which is a JavaScript object with a standardized set of properties, a Reporter uses this information to log your message to its target format while having complete freedom as to how this data should be formatted and displayed.

There is a function for each type of log you can output, these functions are verbose(diagnostic), info(diagnostic), log(diagnostic), warn(diagnostic) and error(diagnostic). These log levels are used for defining the severity of your log message, this is useful for formatting and filtering. For example, the end user can use the flag --log-level to define which messages it wants to see. Each of these functions also have a single parameter called diagnostic, this parameter 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 probably want to throw a ThrowableDiagnostic instead logger.error(...) or throw ThrowableDiagnostic(...)

Logger API

Automatically collected logs and errors

Parcel core automatically collects any logs created by calling the global variable console, this means whenever you do a console.log we internally catch this and convert it to a Diagnostic object. This is not recommended as we do not have as much information as we do when calling the logger instance directly. We also do the same for errors, whenever you throw an error we convert it into a Diagnostic, append information about the plugin to it and send it to the logger.

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 an error. This object can includes a message, information about the file, a codeframe, error information and hints on how to potentially resolve the issue.

Formatting the messages

To format the messages in a diagnostic, we use a very minimal version of markdown specifically built to be compatible with terminals and anything else, while also not being too cryptic when displayed without any formatting. For our @parcel/reporter-cli we use our own @parcel/markdown-ansi library that converts these markdown strings to ANSI escape sequences.

The supported markdown features are **bold**, *italic*/_italic_, __underlined__ and ~~strikethrough~~.

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 codeframes and hints so your users don't have to search the web for hours to find a solution.

For errors you can also throw an error that has been augmented with diagnostic information, this is very useful for failing a build while also providing a useful error message, with a codeframe and suggestions. This is also the recommended way of throwing errors in Parcel.

An example for every method you can use:

import { Transformer } from "@parcel/plugin";
import ThrowableDiagnostic from "@parcel/diagnostic";

export default new Transformer({
// ...

async transform({ asset, ast, config, logger, resolve, options }) {
logger.verbose({
message: "Started transforming a dummy asset",
filePath: asset.filePath,
language: asset.type,
});

logger.info([
{
message: "This is an example message",
filePath: asset.filePath,
language: asset.type,
},
{
message: "This is another example message",
filePath: asset.filePath,
language: asset.type,
},
]);

logger.warn({
message: "This is an example message",
filePath: asset.filePath,
language: asset.type,
});

try {
loadDummyCompiler();
} catch (err) {
throw new ThrowableDiagnostic({
diagnostic: {
message: err.message,
filePath: asset.filePath,
language: asset.type,
stack: err.stack,
name: err.name,
codeFrame: {
code: await asset.getCode(),
codeHighlights: [
{
start: {
line: 1,
column: 5,
},
end: {
line: 2,
column: 3,
},
message: "This is an example message inside a **codeframe**",
},
],
},
hints: [
"Install __@dummy/compiler__ using *npm install @dummy/compiler*",
],
},
});
}

// ...
},
});

API

DiagnosticHighlightLocation parcel/packages/core/diagnostic/src/diagnostic.js:9

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:14

Type
type DiagnosticSeverity = 'error' | 'warn' | 'info';

DiagnosticCodeHighlight parcel/packages/core/diagnostic/src/diagnostic.js:20

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:34

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 Diagnostic#filePath, remember that the asset's current code could be different from the input contents.
  codeHighlights: Array<DiagnosticCodeHighlight>,
|}
Referenced by:
Diagnostic

Diagnostic parcel/packages/core/diagnostic/src/diagnostic.js:50

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)
  filePath?: FilePath,
Path to the file this diagnostic is about (optional, absolute or relative to the project root)
  language?: string,
Language of the file this diagnostic is about (optional)
  codeFrame?: 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
|}
Referenced by:
BuildFailureEvent, DiagnosticCodeFrame, DiagnosticLogEvent, DiagnosticWithoutOrigin, Diagnostifiable, ResolveResult, ThrowableDiagnostic, ThrowableDiagnosticOpts, ValidateResult, anyToDiagnostic, errorToDiagnostic

PrintableError parcel/packages/core/diagnostic/src/diagnostic.js:77

Type
type PrintableError = 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 | string;
Referenced by:
PluginLogger, anyToDiagnostic

anyToDiagnostic parcel/packages/core/diagnostic/src/diagnostic.js:105

Normalize the given value into a diagnostic.

Type
function anyToDiagnostic(input: Diagnostifiable): Array<Diagnostic> {}

errorToDiagnostic parcel/packages/core/diagnostic/src/diagnostic.js:119

Normalize the given error into a diagnostic.

Type
function errorToDiagnostic(error: ThrowableDiagnostic | PrintableError | string, defaultValues: {|
  origin?: ?string,
  filePath?: ?string,
|} = { ...null
}): Array<Diagnostic> {}

ThrowableDiagnosticOpts parcel/packages/core/diagnostic/src/diagnostic.js:181

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

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

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:215

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 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:240

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:268

Sanitizes object keys before using them as key in generateJSONCodeHighlights

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

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

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

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

Type
type TemplateInput = any;
Referenced by:
md

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

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