Skip to content
Merged
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 { LogOutput 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
76 changes: 45 additions & 31 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,28 @@ export const log = function (
return
}

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

return
}

console.log(stringC)
}

export type LogOutput = Pick<BufferedLogs, 'stderr' | 'stdout'>

// 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): LogOutput => {
if (!logs || !logsAreBuffered(logs)) {
return { stdout: [], stderr: [] }
}

return { stdout: logs.stdout, stderr: logs.stderr }
}

const serializeIndentedArray = function (array) {
return serializeArray(array.map(serializeIndentedItem))
}
Expand All @@ -75,61 +97,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 +184,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 Expand Up @@ -192,15 +214,7 @@ export const getSystemLogger = function (
return (...args) => fileDescriptor.write(`${reduceLogLines(args)}\n`)
}

export const addOutputFlusher = (logs: Logs, outputFlusher: OutputFlusher): Logs => {
if (logsAreBuffered(logs)) {
return {
...logs,
outputFlusher,
}
}

return {
outputFlusher,
}
}
export const addOutputFlusher = (logs: Logs, outputFlusher: OutputFlusher): Logs => ({
...logs,
outputFlusher,
})
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
name: test
inputs: []
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[[plugins]]
package = "./plugin"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default async () => new Response("Hello")

export const config = {
path: "/hello"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const onPreBuild = function () {
console.log('test')
}
26 changes: 26 additions & 0 deletions packages/build/tests/log/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,29 @@ 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/with_plugin_and_functions')
.withFlags({ logger, verbose: true })
.runBuildProgrammatic()

t.deepEqual(result.logs.stdout, [])
t.deepEqual(result.logs.stderr, [])

t.true(logs.length > 0)

// From main logic.
t.true(logs.some((log) => log.includes('Netlify Build')))
t.true(logs.some((log) => log.includes('onPreBuild')))

// From core step.
t.true(logs.some((log) => log.includes('Packaging Functions from ')))

// From plugin.
t.true(logs.some((log) => log.includes('Step started.')))
t.true(logs.some((log) => log.includes('Step ended.')))
})
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