Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve logger (deduping, new help and version) #2737

Merged
merged 13 commits into from
Mar 9, 2022
5 changes: 5 additions & 0 deletions .changeset/neat-snakes-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Astro's logger has been redesigned for an improved experience! In addition to deduping identical messages, we've surfaced more error details and exposed new events like `update` (for in-place HMR) and `reload` (for full-reload HMR).
85 changes: 60 additions & 25 deletions packages/astro/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { AstroConfig } from '../@types/astro';
import { enableVerboseLogging, LogOptions } from '../core/logger.js';

import * as colors from 'kleur/colors';
import fs from 'fs';
import yargs from 'yargs-parser';
import { z } from 'zod';
import { defaultLogDestination } from '../core/logger.js';
Expand All @@ -13,40 +12,76 @@ import devServer from '../core/dev/index.js';
import preview from '../core/preview/index.js';
import { check } from './check.js';
import { formatConfigError, loadConfig } from '../core/config.js';
import { pad } from '../core/dev/util.js';

type Arguments = yargs.Arguments;
type CLICommand = 'help' | 'version' | 'dev' | 'build' | 'preview' | 'reload' | 'check';

/** Display --help flag */
function printHelp() {
console.log(` ${colors.bold('astro')} - Futuristic web development tool.
${colors.bold('Commands:')}
astro dev Run Astro in development mode.
astro build Build a pre-compiled production version of your site.
astro preview Preview your build locally before deploying.
astro check Check your project for errors.

${colors.bold('Flags:')}
--config <path> Specify the path to the Astro config file.
--project-root <path> Specify the path to the project root folder.
--no-sitemap Disable sitemap generation (build only).
--experimental-static-build A more performant build that expects assets to be define statically.
--experimental-ssr Enable SSR compilation.
--drafts Include markdown draft pages in the build.
--verbose Enable verbose logging
--silent Disable logging
--version Show the version number and exit.
--help Show this help message.
`);
linebreak()
headline('astro', 'Futuristic web development tool.');
linebreak()
title('Commands');
table([
['dev', 'Run Astro in development mode.'],
['build', 'Build a pre-compiled production-ready site.'],
['preview', 'Preview your build locally before deploying.'],
['check', 'Check your project for errors.'],
['--version', 'Show the version number and exit.'],
['--help', 'Show this help message.'],
], { padding: 28, prefix: ' astro ' });
linebreak()
title('Flags');
table([
['--config <path>', 'Specify the path to the Astro config file.'],
['--project-root <path>', 'Specify the path to the project root folder.'],
['--no-sitemap', 'Disable sitemap generation (build only).'],
['--legacy-build', 'Use the build strategy prior to 0.24.0'],
['--experimental-ssr', 'Enable SSR compilation.'],
['--drafts', 'Include markdown draft pages in the build.'],
['--verbose', 'Enable verbose logging'],
['--silent', 'Disable logging'],
], { padding: 28, prefix: ' ' });

// Logging utils
function linebreak() {
console.log();
}

function headline(name: string, tagline: string) {
console.log(` ${colors.bgGreen(colors.black(` ${name} `))} ${colors.green(`v${process.env.PACKAGE_VERSION ?? ''}`)} ${tagline}`);
}
function title(label: string) {
console.log(` ${colors.bgWhite(colors.black(` ${label} `))}`);
}
function table(rows: [string, string][], opts: { padding: number, prefix: string }) {
const split = rows.some(row => {
const message = `${opts.prefix}${' '.repeat(opts.padding)}${row[1]}`;
return message.length > process.stdout.columns;
})
for (const row of rows) {
row.forEach((col, i) => {
if (i === 0) {
process.stdout.write(`${opts.prefix}${colors.bold(pad(`${col}`, opts.padding - opts.prefix.length))}`)
} else {
if (split) {
process.stdout.write('\n ');
}
process.stdout.write(colors.dim(col) + '\n')
}
})
}
return '';
}
}

/** Display --version flag */
async function printVersion() {
const pkgURL = new URL('../../package.json', import.meta.url);
const pkg = JSON.parse(await fs.promises.readFile(pkgURL, 'utf8'));
const pkgVersion = pkg.version;

console.log(pkgVersion);
// PACKAGE_VERSION is injected at build time
const version = process.env.PACKAGE_VERSION ?? '';
console.log();
console.log(` ${colors.bgGreen(colors.black(` astro `))} ${colors.green(`v${version}`)}`);
}

/** Determine which command the user requested */
Expand Down
58 changes: 44 additions & 14 deletions packages/astro/src/core/logger.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { CompileError } from '@astrojs/parser';

import { bold, blue, dim, red, grey, underline, yellow } from 'kleur/colors';
import { bold, cyan, dim, red, grey, underline, yellow, reset } from 'kleur/colors';
import { performance } from 'perf_hooks';
import { Writable } from 'stream';
import stringWidth from 'string-width';
Expand All @@ -25,8 +25,11 @@ function getLoggerLocale(): string {
const dt = new Intl.DateTimeFormat(getLoggerLocale(), {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});

let lastMessage: string;
let lastMessageCount = 1;
export const defaultLogDestination = new Writable({
objectMode: true,
write(event: LogMessage, _, callback) {
Expand All @@ -35,22 +38,49 @@ export const defaultLogDestination = new Writable({
dest = process.stdout;
}

let type = event.type;
if (type) {
// hide timestamp when type is undefined
dest.write(dim(dt.format(new Date()) + ' '));
if (event.level === 'info') {
type = bold(blue(type));
} else if (event.level === 'warn') {
type = bold(yellow(type));
} else if (event.level === 'error') {
type = bold(red(type));
function getPrefix() {
let prefix = '';
let type = event.type;
if (type) {
// hide timestamp when type is undefined
prefix += dim(dt.format(new Date()) + ' ');
if (event.level === 'info') {
type = bold(cyan(`[${type}]`));
} else if (event.level === 'warn') {
type = bold(yellow(`[${type}]`));
} else if (event.level === 'error') {
type = bold(red(`[${type}]`));
}

prefix += `${type} `;
}

dest.write(`[${type}] `);
return reset(prefix);
}

dest.write(utilFormat(...event.args));
let message = utilFormat(...event.args);
// For repeat messages, only update the message counter
if (message === lastMessage) {
lastMessageCount++;
if (levels[event.level] < levels['error']) {
let lines = 1;
let len = stringWidth(`${getPrefix()}${message}`);
let cols = (dest as typeof process.stdout).columns;
if (len > cols) {
lines = Math.ceil(len / cols);
}
for (let i = 0; i < lines; i++) {
(dest as typeof process.stdout).clearLine(0);
(dest as typeof process.stdout).cursorTo(0);
(dest as typeof process.stdout).moveCursor(0, -1);
}
}
message = `${message} ${yellow(`(x${lastMessageCount})`)}`
} else {
lastMessage = message;
lastMessageCount = 1;
}
dest.write(getPrefix())
dest.write(message);
dest.write('\n');

callback();
Expand Down
53 changes: 30 additions & 23 deletions packages/astro/src/core/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,28 @@
*/

import type { AddressInfo } from 'net';
import { bold, dim, green, magenta, yellow, cyan } from 'kleur/colors';
import stripAnsi from 'strip-ansi';
import { bold, dim, red, green, magenta, yellow, cyan, bgGreen, black } from 'kleur/colors';
import { pad, emoji } from './dev/util.js';

const PREFIX_PADDING = 6;

/** Display */
export function req({ url, statusCode, reqTime }: { url: string; statusCode: number; reqTime?: number }): string {
let color = dim;
if (statusCode >= 500) color = magenta;
if (statusCode >= 500) color = red;
else if (statusCode >= 400) color = yellow;
else if (statusCode >= 300) color = dim;
else if (statusCode >= 200) color = green;
return `${color(statusCode)} ${pad(url, 40)} ${reqTime ? dim(Math.round(reqTime) + 'ms') : ''}`;
return `${bold(color(pad(`${statusCode}`, PREFIX_PADDING)))} ${pad(url, 40)} ${reqTime ? dim(Math.round(reqTime) + 'ms') : ''}`.trim();
}

/** Display */
export function reload({ url, reqTime }: { url: string; reqTime: number }): string {
let color = yellow;
return `${pad(url, 40)} ${dim(Math.round(reqTime) + 'ms')}`;
export function reload({ file }: { file: string }): string {
return `${green(pad('reload', PREFIX_PADDING))} ${file}`;
}

export function hmr({ file }: { file: string }): string {
return `${green(pad('update', PREFIX_PADDING))} ${file}`;
}

/** Display dev server host and startup time */
Expand All @@ -39,29 +44,31 @@ export function devStart({
site: URL | undefined;
}): string {
// PACAKGE_VERSION is injected at build-time
const pkgVersion = process.env.PACKAGE_VERSION;

const version = process.env.PACKAGE_VERSION ?? '0.0.0';
const rootPath = site ? site.pathname : '/';
const toDisplayUrl = (hostname: string) => `${https ? 'https' : 'http'}:https://${hostname}:${port}${rootPath}`;
const messages = [
``,
`${emoji('🚀 ', '')}${magenta(`astro ${pkgVersion}`)} ${dim(`started in ${Math.round(startupTime)}ms`)}`,
``,
`Local: ${bold(cyan(toDisplayUrl(localAddress)))}`,
`Network: ${bold(cyan(toDisplayUrl(networkAddress)))}`,
``,
`${emoji('🚀 ', '')}${bgGreen(black(` astro `))} ${green(`v${version}`)} ${dim(`started in ${Math.round(startupTime)}ms`)}`,
'',
`${dim('┃')} Local ${bold(cyan(toDisplayUrl(localAddress)))}`,
`${dim('┃')} Network ${bold(cyan(toDisplayUrl(networkAddress)))}`,
'',
];
return messages.join('\n');
}

/** Display dev server host */
export function devHost({ address, https, site }: { address: AddressInfo; https: boolean; site: URL | undefined }): string {
const rootPath = site ? site.pathname : '/';
const displayUrl = `${https ? 'https' : 'http'}:https://${address.address}:${address.port}${rootPath}`;
return `Local: ${bold(magenta(displayUrl))}`;
return messages.map(msg => ` ${msg}`).join('\n');
}

/** Display port in use */
export function portInUse({ port }: { port: number }): string {
return `Port ${port} in use. Trying a new one…`;
}

/** Pretty-print errors */
export function err(error: Error): string {
if (!error.stack) return stripAnsi(error.message);
let message = stripAnsi(error.message);
let stack = stripAnsi(error.stack);
const split = stack.indexOf(message) + message.length;
message = stack.slice(0, split);
stack = stack.slice(split).replace(/^\n+/, '');
return `${message}\n${dim(stack)}`;
}
11 changes: 6 additions & 5 deletions packages/astro/src/vite-plugin-astro-server/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type * as vite from 'vite';
import type http from 'http';
import type { AstroConfig, ManifestData, RouteData } from '../@types/astro';
import { info, LogOptions } from '../core/logger.js';
import type { AstroConfig, ManifestData } from '../@types/astro';
import { info, error, LogOptions } from '../core/logger.js';
import { createRouteManifest, matchRoute } from '../core/routing/index.js';
import stripAnsi from 'strip-ansi';
import { createSafeError } from '../core/util.js';
Expand Down Expand Up @@ -81,7 +81,7 @@ async function handleRequest(
const rootRelativeUrl = pathname.substring(devRoot.length - 1);
try {
if (!pathname.startsWith(devRoot)) {
info(logging, 'astro', msg.req({ url: pathname, statusCode: 404 }));
info(logging, 'serve', msg.req({ url: pathname, statusCode: 404 }));
return handle404Response(origin, config, req, res);
}
// Attempt to match the URL to a valid page route.
Expand All @@ -95,7 +95,7 @@ async function handleRequest(
}
// If still no match is found, respond with a generic 404 page.
if (!route) {
info(logging, 'astro', msg.req({ url: pathname, statusCode: 404 }));
info(logging, 'serve', msg.req({ url: pathname, statusCode: 404 }));
handle404Response(origin, config, req, res);
return;
}
Expand All @@ -113,8 +113,9 @@ async function handleRequest(
});
writeHtmlResponse(res, statusCode, html);
} catch (_err: any) {
info(logging, 'astro', msg.req({ url: pathname, statusCode: 500 }));
info(logging, 'serve', msg.req({ url: pathname, statusCode: 500 }));
const err = createSafeError(_err);
error(logging, 'error', msg.err(err))
handle500Response(viteServer, origin, req, res, err);
}
}
Expand Down
16 changes: 10 additions & 6 deletions packages/astro/src/vite-plugin-astro/hmr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import type { LogOptions } from '../core/logger.js';
import type { ViteDevServer, ModuleNode, HmrContext } from 'vite';
import type { PluginContext as RollupPluginContext, ResolvedId } from 'rollup';
import { invalidateCompilation, isCached } from './compile.js';
import { logger } from '../core/logger.js';
import { green } from 'kleur/colors';
import { info } from '../core/logger.js';
import * as msg from '../core/messages.js';

interface TrackCSSDependenciesOptions {
viteDevServer: ViteDevServer | null;
Expand Down Expand Up @@ -46,7 +46,7 @@ export async function trackCSSDependencies(this: RollupPluginContext, opts: Trac
}
}

export function handleHotUpdate(ctx: HmrContext, config: AstroConfig, logging: LogOptions) {
export async function handleHotUpdate(ctx: HmrContext, config: AstroConfig, logging: LogOptions) {
// Invalidate the compilation cache so it recompiles
invalidateCompilation(config, ctx.file);

Expand Down Expand Up @@ -79,11 +79,15 @@ export function handleHotUpdate(ctx: HmrContext, config: AstroConfig, logging: L
invalidateCompilation(config, file);
}

const mod = ctx.modules.find(m => m.file === ctx.file);
const file = ctx.file.replace(config.projectRoot.pathname, '/');
if (ctx.file.endsWith('.astro')) {
const file = ctx.file.replace(config.projectRoot.pathname, '/');
logger.info('astro', green('hmr'), `${file}`);
ctx.server.ws.send({ type: 'custom', event: 'astro:update', data: { file } });
}

if (mod?.isSelfAccepting) {
info(logging, 'astro', msg.hmr({ file }));
} else {
info(logging, 'astro', msg.reload({ file }));
}
return Array.from(filtered);
}
2 changes: 1 addition & 1 deletion packages/astro/test/astro-doctype.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('Doctype', () => {
expect(html).not.to.match(/<\/!DOCTYPE>/i);
});

it('Doctype can be provided in a layout', async () => {
it.skip('Doctype can be provided in a layout', async () => {
const html = await fixture.readFile('/in-layout/index.html');

// test 1: doctype is at the front
Expand Down
Loading