Skip to content

Commit

Permalink
Redesign nextjs logging (#54713)
Browse files Browse the repository at this point in the history
The current logging styles has been existed for a while, this PR gives a fresh impression for the logging output from Next.js.
We want to achieve few new goals that makes the output clean, modernized, sweet 🍫 .

Few goals are addressed with this redesign:

## Refresh Impression & Simplification

The new design of logging is much more information centralized and streamlined.

* Given a `ready` message at the begining when compilers are bootstrapped.
* Only show `compiled` event with green check mark indicating succesful compilation, this will merge the unclear `compiling` event which shows `(client and server)` before, now tell you the route compilation info in one line.

hello world app

### `next dev`

#### After vs Before


<img src="https://github.com/vercel/next.js/assets/4800338/9649b340-8241-4756-a2b3-a989f0b74003" height="120"> 
<img src="https://github.com/vercel/next.js/assets/4800338/ee181263-3dd4-40d0-9ffc-819a56b45900" height="120">  

 


 

### `next build`

#### After vs Before


<img src="https://github.com/vercel/next.js/assets/4800338/5db9829a-9ffc-49f0-b030-93ee92f5c248" width="360"> 
<img src="https://github.com/vercel/next.js/assets/4800338/b9527b83-27c8-4426-9c0d-c0d4072b7d58" width="360">





### error status

#### After vs Before

<img src="https://github.com/vercel/next.js/assets/4800338/00455226-ace7-468b-8d90-0d36bf038489" height="120"> 
<img src="https://github.com/vercel/next.js/assets/4800338/1be8c451-d3f0-465c-9ef7-6b0dde7cff85" height="120"> 



## Streamlization

If you have customized envs and experiments Next.js will give the brief in the early summary about your network information, env vars, and enabled experimental features

<img src="https://github.com/vercel/next.js/assets/4800338/ca1a7409-1532-46cb-850f-687e61e587b2" width="400">


## Polish

### fetching logging structure 

#### After vs Before
<img src="https://github.com/vercel/next.js/assets/4800338/97526397-dffe-4736-88ed-e5cbe5e945bd" width="400">
<img src="https://github.com/vercel/next.js/assets/4800338/ab77c907-5ab5-48bb-8347-6146d2e60932" width="400">


### Dedupe Duplicates

The logging is moved from `@next/env` to `next` itself, `@next/env` will only notify the invoker that the env is reloaded. Then the duplicated logs for the env reloading cases can be avoid.

#### After vs Before
<img src="https://github.com/vercel/next.js/assets/4800338/04799295-e739-4035-87aa-61cec962fc39" width="400">
<img src="https://github.com/vercel/next.js/assets/4800338/e29020c9-0031-4bf3-a21b-8b64633f43a2" width="400"> 


### Different indicators

Use unicode text icons for different situation: 
* passed -> check mark
* warning -> warning
* error -> red cross
* loading -> circle

<img src="https://github.com/vercel/next.js/assets/4800338/715c34bd-298f-4990-a5d7-e12e455ead44" width="400">



Co-authored-by: Tim Neutkens <6324199+timneutkens@users.noreply.github.com>
  • Loading branch information
huozhi and timneutkens committed Sep 5, 2023
1 parent 930db5c commit e117c00
Show file tree
Hide file tree
Showing 67 changed files with 556 additions and 420 deletions.
16 changes: 12 additions & 4 deletions packages/next-env/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ export function processEnv(
loadedEnvFiles: LoadedEnvFiles,
dir?: string,
log: Log = console,
forceReload = false
forceReload = false,
onReload?: (envFilePath: string) => void
) {
if (!initialEnv) {
initialEnv = Object.assign({}, process.env)
Expand Down Expand Up @@ -76,7 +77,7 @@ export function processEnv(
item.contents === envFile.contents && item.path === envFile.path
)
) {
log.info(`Loaded env from ${path.join(dir || '', envFile.path)}`)
onReload?.(envFile.path)
}

for (const key of Object.keys(result.parsed || {})) {
Expand Down Expand Up @@ -109,7 +110,8 @@ export function loadEnvConfig(
dir: string,
dev?: boolean,
log: Log = console,
forceReload = false
forceReload = false,
onReload?: (envFilePath: string) => void
): {
combinedEnv: Env
loadedEnvFiles: LoadedEnvFiles
Expand Down Expand Up @@ -160,6 +162,12 @@ export function loadEnvConfig(
}
}
}
combinedEnv = processEnv(cachedLoadedEnvFiles, dir, log, forceReload)
combinedEnv = processEnv(
cachedLoadedEnvFiles,
dir,
log,
forceReload,
onReload
)
return { combinedEnv, loadedEnvFiles: cachedLoadedEnvFiles }
}
3 changes: 2 additions & 1 deletion packages/next-swc/crates/next-dev-tests/tests-manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ const enabledTests = [
'test/integration/catches-missing-getStaticProps/test/index.test.js',
'test/integration/clean-distdir/test/index.test.js',
'test/integration/client-navigation-a11y/test/index.test.js',
'test/integration/config-experimental-warning/test/index.test.js',
// TODO: re-enable once the logging is aligned
// 'test/integration/config-experimental-warning/test/index.test.js',
'test/integration/config-schema-check/test/index.test.js',
'test/integration/config-syntax-error/test/index.test.js',
'test/integration/config-validation/test/index.test.ts',
Expand Down
12 changes: 3 additions & 9 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,9 +481,7 @@ export default async function build(
} as any

if (!isGenerate) {
buildSpinner = createSpinner({
prefixText: `${Log.prefixes.info} Creating an optimized production build`,
})
buildSpinner = createSpinner('Creating an optimized production build')
}

NextBuildContext.buildSpinner = buildSpinner
Expand Down Expand Up @@ -1125,9 +1123,7 @@ export default async function build(
await startTypeChecking(typeCheckingOptions)
}

const postCompileSpinner = createSpinner({
prefixText: `${Log.prefixes.info} Collecting page data`,
})
const postCompileSpinner = createSpinner('Collecting page data')

const buildManifestPath = path.join(distDir, BUILD_MANIFEST)
const appBuildManifestPath = path.join(distDir, APP_BUILD_MANIFEST)
Expand Down Expand Up @@ -2597,9 +2593,7 @@ export default async function build(

await exportApp(dir, exportOptions, nextBuildSpan)

const postBuildSpinner = createSpinner({
prefixText: `${Log.prefixes.info} Finalizing page optimization`,
})
const postBuildSpinner = createSpinner('Finalizing page optimization')
ssgNotFoundPaths = exportConfig.ssgNotFoundPaths

// remove server bundles that were exported
Expand Down
5 changes: 1 addition & 4 deletions packages/next/src/build/output/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ buildStore.subscribe((state) => {
{
bootstrap: false,
appUrl: appUrl!,
// If it takes more than 3 seconds to compile, mark it as loading status
loading: true,
trigger,
} as OutputState,
Expand All @@ -138,10 +139,6 @@ buildStore.subscribe((state) => {
appUrl: appUrl!,
loading: false,
typeChecking: false,
partial:
clientWasLoading && (serverWasLoading || edgeServerWasLoading)
? 'client and server'
: undefined,
totalModulesCount:
(clientWasLoading ? client.totalModulesCount : 0) +
(serverWasLoading ? server.totalModulesCount : 0) +
Expand Down
20 changes: 12 additions & 8 deletions packages/next/src/build/output/log.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import chalk from '../../lib/chalk'

export const prefixes = {
wait: '- ' + chalk.cyan('wait'),
error: '- ' + chalk.red('error'),
warn: '- ' + chalk.yellow('warn'),
ready: '- ' + chalk.green('ready'),
info: '- ' + chalk.cyan('info'),
event: '- ' + chalk.magenta('event'),
trace: '- ' + chalk.magenta('trace'),
wait: chalk.white(chalk.bold('○')),
error: chalk.red(chalk.bold('X')),
warn: chalk.yellow(chalk.bold('⚠')),
ready: chalk.bold('▲'), // no color
info: chalk.white(chalk.bold(' ')),
event: chalk.green(chalk.bold('✓')),
trace: chalk.magenta(chalk.bold('»')),
} as const

const LOGGING_METHOD = {
Expand All @@ -31,10 +31,14 @@ function prefixedLog(prefixType: keyof typeof prefixes, ...message: any[]) {
if (message.length === 0) {
console[consoleMethod]('')
} else {
console[consoleMethod](prefix, ...message)
console[consoleMethod](' ' + prefix, ...message)
}
}

export function bootstrap(...message: any[]) {
console.log(' ', ...message)
}

export function wait(...message: any[]) {
prefixedLog('wait', ...message)
}
Expand Down
44 changes: 26 additions & 18 deletions packages/next/src/build/output/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
} from '../swc'
import * as Log from './log'

const MAX_DURATION = 3 * 1000

export type OutputState =
| { bootstrap: true; appUrl: string | null; bindAddr: string | null }
| ({ bootstrap: false; appUrl: string | null; bindAddr: string | null } & (
Expand All @@ -18,7 +20,6 @@ export type OutputState =
| {
loading: false
typeChecking: boolean
partial: 'client and server' | undefined
totalModulesCount: number
errors: string[] | null
warnings: string[] | null
Expand Down Expand Up @@ -49,26 +50,29 @@ function hasStoreChanged(nextStore: OutputState) {
}

let startTime = 0
let trigger = '' // default, use empty string for trigger
let loadingLogTimer: NodeJS.Timeout | null = null

store.subscribe((state) => {
if (!hasStoreChanged(state)) {
return
}

if (state.bootstrap) {
if (state.appUrl) {
Log.ready(`started server on ${state.bindAddr}, url: ${state.appUrl}`)
}
return
}

if (state.loading) {
if (state.trigger) {
if (state.trigger !== 'initial') {
Log.wait(`compiling ${state.trigger}...`)
trigger = state.trigger
if (trigger !== 'initial') {
if (!loadingLogTimer) {
// Only log compiling if compiled is not finished in 3 seconds
loadingLogTimer = setTimeout(() => {
Log.wait(`compiling ${trigger} ...`)
}, MAX_DURATION)
}
}
} else {
Log.wait('compiling...')
}
if (startTime === 0) {
startTime = Date.now()
Expand Down Expand Up @@ -107,19 +111,15 @@ store.subscribe((state) => {
startTime = 0

timeMessage =
time > 2000 ? ` in ${Math.round(time / 100) / 10}s` : ` in ${time} ms`
' ' +
(time > 2000 ? `in ${Math.round(time / 100) / 10}s` : `in ${time}ms`)
}

let modulesMessage = ''
if (state.totalModulesCount) {
modulesMessage = ` (${state.totalModulesCount} modules)`
}

let partialMessage = ''
if (state.partial) {
partialMessage = ` ${state.partial}`
}

if (state.warnings) {
Log.warn(state.warnings.join('\n\n'))
// Ensure traces are flushed after each compile in development mode
Expand All @@ -132,14 +132,22 @@ store.subscribe((state) => {

if (state.typeChecking) {
Log.info(
`bundled${partialMessage} successfully${timeMessage}${modulesMessage}, waiting for typecheck results...`
`bundled ${trigger}${timeMessage}${modulesMessage}, type checking...`
)
return
}

Log.event(
`compiled${partialMessage} successfully${timeMessage}${modulesMessage}`
)
if (trigger === 'initial') {
Log.event('ready')
trigger = ''
} else if (trigger) {
if (loadingLogTimer) {
clearTimeout(loadingLogTimer)
loadingLogTimer = null
}
Log.event(`compiled ${trigger}${timeMessage}${modulesMessage}`)
}

// Ensure traces are flushed after each compile in development mode
flushAllTraces()
teardownTraceSubscriber()
Expand Down
19 changes: 15 additions & 4 deletions packages/next/src/build/spinner.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import ora from 'next/dist/compiled/ora'
import * as Log from './output/log'

const dotsSpinner = {
frames: ['.', '..', '...'],
interval: 200,
}

export default function createSpinner(
text: string | { prefixText: string },
text: string,
options: ora.Options = {},
logFn: (...data: any[]) => void = console.log
) {
let spinner: undefined | ora.Ora
let prefixText = text && typeof text === 'object' && text.prefixText

const prefixText = ` ${Log.prefixes.info} ${text}`
// Add \r at beginning to reset the current line of loading status text
const suffixText = `\r ${Log.prefixes.event} ${text}`

if (process.stdout.isTTY) {
spinner = ora({
text: typeof text === 'string' ? text : undefined,
prefixText: typeof prefixText === 'string' ? prefixText : undefined,
text: undefined,
prefixText,
spinner: dotsSpinner,
stream: process.stdout,
...options,
Expand Down Expand Up @@ -51,6 +55,13 @@ export default function createSpinner(
return spinner!
}
spinner.stopAndPersist = (): ora.Ora => {
if (suffixText) {
if (spinner) {
spinner.text = suffixText
} else {
logFn(suffixText)
}
}
origStopAndPersist()
resetLog()
return spinner!
Expand Down
8 changes: 5 additions & 3 deletions packages/next/src/build/swc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ const infoLog = (...args: any[]) => {
if (process.env.NEXT_PRIVATE_BUILD_WORKER) {
return
}
Log.info(...args)
if (process.env.DEBUG) {
Log.info(...args)
}
}

/**
Expand Down Expand Up @@ -1057,7 +1059,7 @@ async function loadWasm(importPath = '', isCustomTurbopack: boolean) {
if (pkg === '@next/swc-wasm-web') {
bindings = await bindings.default()
}
infoLog('Using wasm build of next-swc')
infoLog('next-swc build: wasm build @next/swc-wasm-web')

// Note wasm binary does not support async intefaces yet, all async
// interface coereces to sync interfaces.
Expand Down Expand Up @@ -1187,7 +1189,7 @@ function loadNative(isCustomTurbopack = false, importPath?: string) {
for (const triple of triples) {
try {
bindings = require(`@next/swc/native/next-swc.${triple.platformArchABI}.node`)
infoLog('Using locally built binary of @next/swc')
infoLog('next-swc build: local built @next/swc')
break
} catch (e) {}
}
Expand Down
6 changes: 3 additions & 3 deletions packages/next/src/build/type-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ export async function startTypeChecking({
// we will not create a spinner if both ignoreTypeScriptErrors and ignoreESLint are
// enabled, but we will still verifying project's tsconfig and dependencies.
if (typeCheckingAndLintingSpinnerPrefixText) {
typeCheckingAndLintingSpinner = createSpinner({
prefixText: `${Log.prefixes.info} ${typeCheckingAndLintingSpinnerPrefixText}`,
})
typeCheckingAndLintingSpinner = createSpinner(
typeCheckingAndLintingSpinnerPrefixText
)
}

const typeCheckStart = process.hrtime()
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/build/webpack-build/impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ export async function webpackBuildImpl(
console.warn()
} else if (!compilerName) {
NextBuildContext.buildSpinner?.stopAndPersist()
Log.info('Compiled successfully')
Log.event('Compiled successfully')
}

return {
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/build/webpack-build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ async function webpackBuildWithWorker() {
}
}
buildSpinner?.stopAndPersist()
Log.info('Compiled successfully')
Log.event('Compiled successfully')

return combinedResult
}
Expand Down
Loading

0 comments on commit e117c00

Please sign in to comment.