astro/src/logger.ts
2021-04-01 10:25:28 -06:00

131 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { CompileError } from './parser/utils/error.js';
import { bold, blue, red, grey, underline } from 'kleur/colors';
import { Writable } from 'stream';
import { format as utilFormat } from 'util';
type ConsoleStream = Writable & {
fd: 1 | 2;
};
export const defaultLogDestination = new Writable({
objectMode: true,
write(event: LogMessage, _, callback) {
let dest: ConsoleStream = process.stderr;
if (levels[event.level] < levels['error']) {
dest = process.stdout;
}
let type = event.type;
if (event.level === 'info') {
type = bold(blue(type));
} else if (event.level === 'error') {
type = bold(red(type));
}
dest.write(`[${type}] `);
dest.write(utilFormat(...event.args));
dest.write('\n');
callback();
},
});
interface LogWritable<T> extends Writable {
write: (chunk: T) => boolean;
}
export type LoggerLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'; // same as Pino
export type LoggerEvent = 'debug' | 'info' | 'warn' | 'error';
export interface LogOptions {
dest: LogWritable<LogMessage>;
level: LoggerLevel;
}
export const defaultLogOptions: LogOptions = {
dest: defaultLogDestination,
level: 'info',
};
export interface LogMessage {
type: string;
level: LoggerLevel;
message: string;
args: Array<any>;
}
const levels: Record<LoggerLevel, number> = {
debug: 20,
info: 30,
warn: 40,
error: 50,
silent: 90,
};
/** Full logging API */
export function log(opts: LogOptions = defaultLogOptions, level: LoggerLevel, type: string, ...args: Array<any>) {
const event: LogMessage = {
type,
level,
args,
message: '',
};
// test if this level is enabled or not
if (levels[opts.level] > levels[level]) {
return; // do nothing
}
opts.dest.write(event);
}
/** Emit a message only shown in debug mode */
export function debug(opts: LogOptions, type: string, ...messages: Array<any>) {
return log(opts, 'debug', type, ...messages);
}
/** Emit a general info message (be careful using this too much!) */
export function info(opts: LogOptions, type: string, ...messages: Array<any>) {
return log(opts, 'info', type, ...messages);
}
/** Emit a warning a user should be aware of */
export function warn(opts: LogOptions, type: string, ...messages: Array<any>) {
return log(opts, 'warn', type, ...messages);
}
/** Emit a fatal error message the user should address. */
export function error(opts: LogOptions, type: string, ...messages: Array<any>) {
return log(opts, 'error', type, ...messages);
}
/** Pretty format error for display */
export function parseError(opts: LogOptions, err: CompileError) {
let frame = err.frame
// Switch colons for pipes
.replace(/^([0-9]+)(:)/gm, `${bold('$1')}`)
// Make the caret red.
.replace(/(?<=^\s+)(\^)/gm, bold(red(' ^')))
// Add identation
.replace(/^/gm, ' ');
error(
opts,
'parse-error',
`
${underline(bold(grey(`${err.filename}:${err.start.line}:${err.start.column}`)))}
${bold(red(`𝘅 ${err.message}`))}
${frame}
`
);
}
// A default logger for when too lazy to pass LogOptions around.
export const logger = {
debug: debug.bind(null, defaultLogOptions),
info: info.bind(null, defaultLogOptions),
warn: warn.bind(null, defaultLogOptions),
error: error.bind(null, defaultLogOptions),
};