diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index e39671037c8c3..8aa9e61891d84 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -132,6 +132,7 @@ export default async function build( .traceChild('load-next-config') .traceAsyncFn(() => loadConfig(PHASE_PRODUCTION_BUILD, dir, conf)) const distDir = path.join(dir, config.distDir) + setGlobal('phase', PHASE_PRODUCTION_BUILD) setGlobal('distDir', distDir) const { target } = config diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index b31c472711736..68fdb8ac757d8 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -286,6 +286,8 @@ export default class DevServer extends Server { } async prepare(): Promise { + setGlobal('distDir', this.distDir) + setGlobal('phase', PHASE_DEVELOPMENT_SERVER) await verifyTypeScriptSetup( this.dir, this.pagesDir!, diff --git a/packages/next/trace/report/to-json.ts b/packages/next/trace/report/to-json.ts index 22cd5d4343ab4..92a16bcfe260c 100644 --- a/packages/next/trace/report/to-json.ts +++ b/packages/next/trace/report/to-json.ts @@ -3,11 +3,64 @@ import { batcher } from './to-zipkin' import { traceGlobals } from '../shared' import fs from 'fs' import path from 'path' +import { PHASE_DEVELOPMENT_SERVER } from '../../shared/lib/constants' -let writeStream: fs.WriteStream +let writeStream: RotatingWriteStream let traceId: string let batch: ReturnType | undefined +const writeStreamOptions = { + flags: 'a', + encoding: 'utf8', +} +class RotatingWriteStream { + file: string + writeStream!: fs.WriteStream + size: number + sizeLimit: number + isRotating: Promise | undefined + constructor(file: string, sizeLimit: number) { + this.file = file + this.size = 0 + this.sizeLimit = sizeLimit + this.createWriteStream() + } + private createWriteStream() { + this.writeStream = fs.createWriteStream(this.file, writeStreamOptions) + } + // Recreate the file + private rotate(): void { + this.end() + try { + fs.unlinkSync(this.file) + } catch (err: any) { + // It's fine if the file does not exist yet + if (err.code !== 'ENOENT') { + throw err + } + } + this.size = 0 + this.createWriteStream() + } + async write(data: string): Promise { + this.size += data.length + + if (this.size > this.sizeLimit) { + this.rotate() + } + + if (!this.writeStream.write(data, 'utf8')) { + await new Promise((resolve, _reject) => { + this.writeStream.once('drain', resolve) + }) + } + } + + end(): void { + this.writeStream.end('', 'utf8') + } +} + const reportToLocalHost = ( name: string, duration: number, @@ -17,7 +70,8 @@ const reportToLocalHost = ( attrs?: Object ) => { const distDir = traceGlobals.get('distDir') - if (!distDir) { + const phase = traceGlobals.get('phase') + if (!distDir || !phase) { return } @@ -30,18 +84,15 @@ const reportToLocalHost = ( if (!writeStream) { await fs.promises.mkdir(distDir, { recursive: true }) const file = path.join(distDir, 'trace') - writeStream = fs.createWriteStream(file, { - flags: 'a', - encoding: 'utf8', - }) + writeStream = new RotatingWriteStream( + file, + // Development is limited to 50MB, production is unlimited + phase === PHASE_DEVELOPMENT_SERVER ? 52428800 : Infinity + ) } const eventsJson = JSON.stringify(events) try { - await new Promise((resolve, reject) => { - writeStream.write(eventsJson + '\n', 'utf8', (err) => { - err ? reject(err) : resolve() - }) - }) + await writeStream.write(eventsJson + '\n') } catch (err) { console.log(err) } @@ -63,7 +114,11 @@ export default { flushAll: () => batch ? batch.flushAll().then(() => { - writeStream.end('', 'utf8') + const phase = traceGlobals.get('phase') + // Only end writeStream when manually flushing in production + if (phase !== PHASE_DEVELOPMENT_SERVER) { + writeStream.end() + } }) : undefined, report: reportToLocalHost,