Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/build/src/core/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { trace, context } from '@opentelemetry/api'

import { handleBuildError } from '../error/handle.js'
import { reportError } from '../error/report.js'
import { getSystemLogger } from '../log/logger.js'
import type { BufferedLogs } from '../log/logger.js'
import { getLogsOutput, getSystemLogger } from '../log/logger.js'
import type { Logs } from '../log/logger.js'
import { logTimer, logBuildSuccess } from '../log/messages/core.js'
import { getGeneratedFunctions } from '../steps/return_values.js'
import { trackBuildComplete } from '../telemetry/main.js'
Expand All @@ -27,7 +27,7 @@ const tracer = trace.getTracer('core')
export async function buildSite(flags: Partial<BuildFlags> = {}): Promise<{
success: boolean
severityCode: number
logs: BufferedLogs | undefined
logs: Logs | undefined
netlifyConfig?: any
configMutations?: any
}> {
Expand Down Expand Up @@ -123,7 +123,7 @@ export async function buildSite(flags: Partial<BuildFlags> = {}): Promise<{
success,
severityCode,
netlifyConfig: netlifyConfigA,
logs,
logs: getLogsOutput(logs),
configMutations,
generatedFunctions: getGeneratedFunctions(returnValues),
}
Expand Down
2 changes: 2 additions & 0 deletions packages/build/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export type BuildCLIFlags = {
export type BuildFlags = BuildCLIFlags & {
env?: Record<string, unknown>
eventHandlers?: EventHandlers
/** Custom logger function to capture build output */
logger?: (message: string) => void
}

type EventHandlers = {
Expand Down
4 changes: 2 additions & 2 deletions packages/build/src/error/monitor/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import Bugsnag from '@bugsnag/js'
import memoizeOne from 'memoize-one'

import type { ResolvedFlags } from '../../core/normalize_flags.js'
import { BufferedLogs, log } from '../../log/logger.js'
import { Logs, log } from '../../log/logger.js'
import { ROOT_PACKAGE_JSON } from '../../utils/json.js'

const projectRoot = fileURLToPath(new URL('../../..', import.meta.url))

// Start a client to monitor errors
export const startErrorMonitor = function (config: { flags: ResolvedFlags; logs?: BufferedLogs; bugsnagKey?: string }) {
export const startErrorMonitor = function (config: { flags: ResolvedFlags; logs?: Logs; bugsnagKey?: string }) {
const {
flags: { mode },
logs,
Expand Down
1 change: 1 addition & 0 deletions packages/build/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { buildSite } from './core/main.js'
export { NetlifyPluginConstants } from './core/constants.js'

export type { BufferedLogs as Logs } from './log/logger.js'
export type { GeneratedFunction } from './steps/return_values.js'
// export the legacy types
export type { NetlifyPlugin } from './types/netlify_plugin.js'
Expand Down
58 changes: 39 additions & 19 deletions packages/build/src/log/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { THEME } from './theme.js'

export type Logs = BufferedLogs | StreamedLogs
export type BufferedLogs = { stdout: string[]; stderr: string[]; outputFlusher?: OutputFlusher }
export type StreamedLogs = { outputFlusher?: OutputFlusher }
export type StreamedLogs = { outputFlusher?: OutputFlusher; logFunction?: (message: string) => void }

export const logsAreBuffered = (logs: Logs | undefined): logs is BufferedLogs => {
return logs !== undefined && 'stdout' in logs
Expand All @@ -31,13 +31,16 @@ const EMPTY_LINE = '\u{200B}'
* When the `buffer` option is true, we return logs instead of printing them
* on the console. The logs are accumulated in a `logs` array variable.
*/
export const getBufferLogs = (config: { buffer?: boolean }): BufferedLogs | undefined => {
const { buffer = false } = config
if (!buffer) {
return
export const getBufferLogs = (config: { buffer?: boolean; logger?: (message: string) => void }): Logs | undefined => {
const { buffer = false, logger } = config

if (logger) {
return { logFunction: logger }
}

return { stdout: [], stderr: [] }
if (buffer) {
return { stdout: [], stderr: [] }
}
}

// Core logging utility, used by the other methods.
Expand All @@ -64,9 +67,26 @@ export const log = function (
return
}

if (typeof logs?.logFunction === 'function') {
logs.logFunction(stringC)

return
}

console.log(stringC)
}

// Returns a `logs` object to be returned in the public interface,
// always containing a `stderr` and `stdout` arrays, regardless of
// whether the `buffer` input property was used.
export const getLogsOutput = (logs: Logs | undefined): BufferedLogs | undefined => {
if (!logs || logsAreBuffered(logs)) {
return logs
}

return { stdout: [], stderr: [] }
}

const serializeIndentedArray = function (array) {
return serializeArray(array.map(serializeIndentedItem))
}
Expand All @@ -75,61 +95,61 @@ const serializeIndentedItem = function (item) {
return indentString(item, INDENT_SIZE + 1).trimStart()
}

export const logError = function (logs: BufferedLogs | undefined, string: string, opts = {}) {
export const logError = function (logs: Logs | undefined, string: string, opts = {}) {
log(logs, string, { color: THEME.errorLine, ...opts })
}

export const logWarning = function (logs: BufferedLogs | undefined, string: string, opts = {}) {
export const logWarning = function (logs: Logs | undefined, string: string, opts = {}) {
log(logs, string, { color: THEME.warningLine, ...opts })
}

// Print a message that is under a header/subheader, i.e. indented
export const logMessage = function (logs: BufferedLogs | undefined, string: string, opts = {}) {
export const logMessage = function (logs: Logs | undefined, string: string, opts = {}) {
log(logs, string, { indent: true, ...opts })
}

// Print an object
export const logObject = function (logs: BufferedLogs | undefined, object, opts) {
export const logObject = function (logs: Logs | undefined, object, opts) {
logMessage(logs, serializeObject(object), opts)
}

// Print an array
export const logArray = function (logs: BufferedLogs | undefined, array, opts = {}) {
export const logArray = function (logs: Logs | undefined, array, opts = {}) {
logMessage(logs, serializeIndentedArray(array), { color: THEME.none, ...opts })
}

// Print an array of errors
export const logErrorArray = function (logs: BufferedLogs | undefined, array, opts = {}) {
export const logErrorArray = function (logs: Logs | undefined, array, opts = {}) {
logMessage(logs, serializeIndentedArray(array), { color: THEME.errorLine, ...opts })
}

// Print an array of warnings
export const logWarningArray = function (logs: BufferedLogs | undefined, array, opts = {}) {
export const logWarningArray = function (logs: Logs | undefined, array, opts = {}) {
logMessage(logs, serializeIndentedArray(array), { color: THEME.warningLine, ...opts })
}

// Print a main section header
export const logHeader = function (logs: BufferedLogs | undefined, string: string, opts = {}) {
export const logHeader = function (logs: Logs | undefined, string: string, opts = {}) {
log(logs, `\n${getHeader(string)}`, { color: THEME.header, ...opts })
}

// Print a main section header, when an error happened
export const logErrorHeader = function (logs: BufferedLogs | undefined, string: string, opts = {}) {
export const logErrorHeader = function (logs: Logs | undefined, string: string, opts = {}) {
logHeader(logs, string, { color: THEME.errorHeader, ...opts })
}

// Print a sub-section header
export const logSubHeader = function (logs: BufferedLogs | undefined, string: string, opts = {}) {
export const logSubHeader = function (logs: Logs | undefined, string: string, opts = {}) {
log(logs, `\n${figures.pointer} ${string}`, { color: THEME.subHeader, ...opts })
}

// Print a sub-section header, when an error happened
export const logErrorSubHeader = function (logs: BufferedLogs | undefined, string: string, opts = {}) {
export const logErrorSubHeader = function (logs: Logs | undefined, string: string, opts = {}) {
logSubHeader(logs, string, { color: THEME.errorSubHeader, ...opts })
}

// Print a sub-section header, when a warning happened
export const logWarningSubHeader = function (logs: BufferedLogs | undefined, string: string, opts = {}) {
export const logWarningSubHeader = function (logs: Logs | undefined, string: string, opts = {}) {
logSubHeader(logs, string, { color: THEME.warningSubHeader, ...opts })
}

Expand Down Expand Up @@ -162,7 +182,7 @@ export const reduceLogLines = function (lines) {
* the user-facing build logs)
*/
export const getSystemLogger = function (
logs: BufferedLogs | undefined,
logs: Logs | undefined,
debug: boolean,
/** A system log file descriptor, if non is provided it will be a noop logger */
systemLogFile?: number,
Expand Down
3 changes: 2 additions & 1 deletion packages/build/src/log/messages/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ const INTERNAL_FLAGS = [
'enhancedSecretScan',
'edgeFunctionsBootstrapURL',
'eventHandlers',
'logger',
]
const HIDDEN_FLAGS = [...SECURE_FLAGS, ...TEST_FLAGS, ...INTERNAL_FLAGS]
const HIDDEN_DEBUG_FLAGS = [...SECURE_FLAGS, ...TEST_FLAGS, 'eventHandlers']
const HIDDEN_DEBUG_FLAGS = [...SECURE_FLAGS, ...TEST_FLAGS, 'eventHandlers', 'logger']

export const logBuildDir = function (logs, buildDir) {
logSubHeader(logs, 'Current directory')
Expand Down
4 changes: 2 additions & 2 deletions packages/build/src/log/messages/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { serializeLogError } from '../../error/parse/serialize_log.js'
import { roundTimerToMillisecs } from '../../time/measure.js'
import { ROOT_PACKAGE_JSON } from '../../utils/json.js'
import { getLogHeaderFunc } from '../header_func.js'
import { log, logMessage, logWarning, logHeader, logSubHeader, logWarningArray, BufferedLogs } from '../logger.js'
import { log, logMessage, logWarning, logHeader, logSubHeader, logWarningArray, Logs } from '../logger.js'
import { OutputFlusher } from '../output_flusher.js'
import { THEME } from '../theme.js'

import { logConfigOnError } from './config.js'

export const logBuildStart = function (logs?: BufferedLogs) {
export const logBuildStart = function (logs?: Logs) {
logHeader(logs, 'Netlify Build')
logSubHeader(logs, 'Version')
logMessage(logs, `${ROOT_PACKAGE_JSON.name} ${ROOT_PACKAGE_JSON.version}`)
Expand Down
20 changes: 20 additions & 0 deletions packages/build/tests/log/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,23 @@ test('Does not truncate long redirects in logs', async (t) => {
const output = await new Fixture('./fixtures/truncate_redirects').runWithBuild()
t.false(output.includes('999'))
})

test('Accepts a custom log function', async (t) => {
const logs = []
const logger = (message) => {
logs.push(message)
}
const result = await new Fixture('./fixtures/verbose').withFlags({ logger, verbose: true }).runBuildProgrammatic()

t.deepEqual(result.logs.stdout, [])
t.deepEqual(result.logs.stderr, [])
t.true(logs.length > 0, 'logger should have been called with messages')
t.true(
logs.some((log) => log.includes('Netlify Build')),
'logs should contain build header',
)
t.true(
logs.some((log) => log.includes('onPreBuild')),
'logs should contain plugin event',
)
})
7 changes: 4 additions & 3 deletions packages/testing/src/fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,10 @@ export class Fixture {

async runWithBuildAndIntrospect(): Promise<Awaited<ReturnType<typeof build>> & { output: string }> {
const buildResult = await build(this.getBuildFlags())
const output = [buildResult.logs?.stdout.join('\n'), buildResult.logs?.stderr.join('\n')]
.filter(Boolean)
.join('\n\n')
const output =
buildResult.logs && 'stdout' in buildResult.logs
? [buildResult.logs.stdout.join('\n'), buildResult.logs.stderr.join('\n')].filter(Boolean).join('\n\n')
: ''

return {
...buildResult,
Expand Down
Loading