diff --git a/packages/build/src/core/build.ts b/packages/build/src/core/build.ts index 45f127fca9..92f941d0a0 100644 --- a/packages/build/src/core/build.ts +++ b/packages/build/src/core/build.ts @@ -2,7 +2,7 @@ import { addAttributesToActiveSpan } from '@netlify/opentelemetry-utils' import { getErrorInfo } from '../error/info.js' import { startErrorMonitor } from '../error/monitor/start.js' -import { getBufferLogs, getSystemLogger } from '../log/logger.js' +import { getBufferLogs, getSystemLogger, structuredLog } from '../log/logger.js' import { logBuildStart } from '../log/messages/core.js' import { loadPlugins } from '../plugins/load.js' import { getPluginsOptions } from '../plugins/options.js' @@ -87,6 +87,7 @@ const tExecBuild = async function ({ edgeFunctionsBootstrapURL, eventHandlers, skewProtectionToken, + framework, }) { const configOpts = getConfigOpts({ config, @@ -157,6 +158,15 @@ const tExecBuild = async function ({ testOpts, } as any) const systemLog = getSystemLogger(logs, debug, systemLogFile) + + // Log framework information + structuredLog({ + logs, + type: 'framework-info', + payload: { framework }, + isRunningLocally: constants.IS_LOCAL, + featureFlags, + }) const pluginsOptions = addCorePlugins({ netlifyConfig, constants }) // `errorParams` is purposely stateful Object.assign(errorParams, { netlifyConfig, pluginsOptions, siteInfo, childEnv, userNodeVersion }) diff --git a/packages/build/src/core/feature_flags.ts b/packages/build/src/core/feature_flags.ts index 5f8e4a0216..d68218c814 100644 --- a/packages/build/src/core/feature_flags.ts +++ b/packages/build/src/core/feature_flags.ts @@ -20,4 +20,5 @@ export const DEFAULT_FEATURE_FLAGS: FeatureFlags = { netlify_build_updated_plugin_compatibility: false, netlify_build_plugin_system_log: false, edge_bundler_generate_tarball: false, + netlify_build_structured_buildbot_logs: true, } diff --git a/packages/build/src/log/logger.ts b/packages/build/src/log/logger.ts index b6199e1eb7..4474e26bfe 100644 --- a/packages/build/src/log/logger.ts +++ b/packages/build/src/log/logger.ts @@ -218,3 +218,34 @@ export const addOutputFlusher = (logs: Logs, outputFlusher: OutputFlusher): Logs ...logs, outputFlusher, }) + +// Prefix used for structured logs that can be parsed by the buildbot +const STRUCTURED_LOG_PREFIX = '___nfslog' + +/** + * Emits a structured log that can be parsed by the buildbot. + * Only logs when running in buildbot mode (not locally) and when the + * `netlify_build_structured_buildbot_logs` feature flag is enabled. + * + * Format: `___nfslog {type} {JSON.stringify(payload)}` + */ +export const structuredLog = function ({ + logs, + type, + payload, + isRunningLocally, + featureFlags, +}: { + logs: Logs | undefined + type: string + payload: Record + isRunningLocally: boolean | string + featureFlags: Record +}): void { + if (isRunningLocally || !featureFlags.netlify_build_structured_buildbot_logs) { + return + } + + const message = `${STRUCTURED_LOG_PREFIX} ${type} ${JSON.stringify(payload)}` + log(logs, message) +} diff --git a/packages/build/src/plugins_core/edge_functions/index.ts b/packages/build/src/plugins_core/edge_functions/index.ts index e5dd15075e..c36ab5e324 100644 --- a/packages/build/src/plugins_core/edge_functions/index.ts +++ b/packages/build/src/plugins_core/edge_functions/index.ts @@ -5,7 +5,7 @@ import { bundle, find } from '@netlify/edge-bundler' import { pathExists } from 'path-exists' import { Metric } from '../../core/report_metrics.js' -import { log, reduceLogLines } from '../../log/logger.js' +import { log, reduceLogLines, structuredLog } from '../../log/logger.js' import { logFunctionsToBundle } from '../../log/messages/core_steps.js' import { FRAMEWORKS_API_EDGE_FUNCTIONS_PATH, @@ -141,6 +141,23 @@ const coreStep = async function ({ systemLog('Edge Functions manifest:', manifest) + // Log bundled edge functions and their configuration + structuredLog({ + logs, + type: 'edge-functions-bundling', + payload: { + functions: Object.entries(manifest.function_config).map(([name, config]: [string, any]) => ({ + name, + generator: config.generator, + path: config.path, + })), + routes: manifest.routes, + postCacheRoutes: manifest.post_cache_routes, + }, + isRunningLocally, + featureFlags, + }) + await validateEdgeFunctionsManifest(manifest, featureFlags) return { metrics } } catch (error) { diff --git a/packages/build/src/plugins_core/functions/index.ts b/packages/build/src/plugins_core/functions/index.ts index 34f80ebe6f..fdc0562c54 100644 --- a/packages/build/src/plugins_core/functions/index.ts +++ b/packages/build/src/plugins_core/functions/index.ts @@ -4,7 +4,7 @@ import { type NodeBundlerName, RUNTIME, zipFunctions, type FunctionResult } from import { pathExists } from 'path-exists' import { addErrorInfo } from '../../error/info.js' -import { log } from '../../log/logger.js' +import { log, structuredLog } from '../../log/logger.js' import { type GeneratedFunction, getGeneratedFunctions } from '../../steps/return_values.js' import { logBundleResults, logFunctionsNonExistingDir, logFunctionsToBundle } from '../../log/messages/core_steps.js' import { FRAMEWORKS_API_FUNCTIONS_PATH } from '../../utils/frameworks_api.js' @@ -116,7 +116,7 @@ const zipFunctionsAndLogResults = async ({ logBundleResults({ logs, results }) - return { bundlers } + return { bundlers, results } } catch (error) { throw await getZipError(error, functionsSrc) } @@ -190,7 +190,7 @@ const coreStep = async function ({ return {} } - const { bundlers } = await zipFunctionsAndLogResults({ + const { bundlers, results } = await zipFunctionsAndLogResults({ branch, buildDir, childEnv, @@ -208,6 +208,27 @@ const coreStep = async function ({ generatedFunctions: generatedFunctions.map((func) => func.path), }) + // Log bundled functions and their configuration + structuredLog({ + logs, + type: 'functions-bundling', + payload: { + functions: results.map((result) => ({ + name: result.name, + runtime: result.runtime, + bundler: result.bundler, + routes: result.routes, + schedule: result.schedule, + displayName: result.displayName, + generator: result.generator, + invocationMode: result.invocationMode, + runtimeVersion: result.runtimeVersion, + })), + }, + isRunningLocally, + featureFlags, + }) + const metrics = getMetrics(internalFunctions, userFunctions) return {