Skip to content

Commit

Permalink
Improve logger (deduping, new help and version) (#2737)
Browse files Browse the repository at this point in the history
* feat: improve logger by removing repeat messages

* feat(hmr): only send HMR updates when files change

* feat: improve hmr formatting

* feat(logger): improve welcome formatting

* feat(logger): improve hmr formatting

* chore(test): update cli test output

* feat(logger): improve logging output

* feat(logger): improve help/version flags

* chore: remove checksum checks

* fix(test): update cli tests

* refactor(test): cleanup astro dev cli tests

* chore: add changeset

* chore(test): skip doctype test
  • Loading branch information
natemoo-re committed Mar 9, 2022
1 parent 8c0a8fe commit e8d4e56
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 76 deletions.
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

0 comments on commit e8d4e56

Please sign in to comment.