This is an automated email from the ASF dual-hosted git repository. young pushed a commit to branch young/chore/show-failed-logs-in-ci in repository https://gitbox.apache.org/repos/asf/apisix-website.git
commit 4b1cf19375650db7e68dabd9ccda800849f52c1e Author: Skye Young <[email protected]> AuthorDate: Tue Oct 28 16:10:43 2025 +0800 chore: show failed logs in ci --- .github/workflows/deploy.yml | 26 ++++++++ scripts/generate-website.js | 137 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 154 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2cb5edb2f50..ba4e2b39827 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -108,6 +108,32 @@ jobs: run: | yarn build + - name: Show build logs on failure + if: failure() + run: | + LOG_DIR="/tmp/apisix-website-build-logs" + if [ -d "$LOG_DIR" ]; then + echo "Build logs:" + for logfile in "$LOG_DIR"/*.log; do + if [ -f "$logfile" ]; then + echo "" + echo "========== $(basename $logfile .log) ==========" + cat "$logfile" + fi + done + else + echo "No log directory found at $LOG_DIR" + fi + + - name: Upload build logs as artifact + if: failure() + uses: actions/upload-artifact@v4 + with: + name: build-logs + path: /tmp/apisix-website-build-logs/ + retention-days: 7 + if-no-files-found: warn + - name: Update sitemap.xml run: | yarn update-sitemap && git status diff --git a/scripts/generate-website.js b/scripts/generate-website.js index 0efa4371ccc..dee5122a0b2 100644 --- a/scripts/generate-website.js +++ b/scripts/generate-website.js @@ -1,8 +1,43 @@ const Listr = require('listr'); const util = require('util'); +const fs = require('fs'); +const path = require('path'); const { chdir } = require('node:process'); const exec = util.promisify(require('node:child_process').exec); +// Detect CI environment +const isCI = process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true'; + +// Control whether to show logs locally (default: true for convenience) +// Set SHOW_BUILD_LOGS=false to disable local log output +const showLogsLocally = process.env.SHOW_BUILD_LOGS !== 'false'; + +// Log directory for build outputs +const logDir = '/tmp/apisix-website-build-logs'; + +// Ensure log directory exists +if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); +} + +// Helper function to print logs from files +function printLogFiles(logFiles, showAll = false) { + /* eslint-disable no-console */ + logFiles.forEach((file) => { + // Only print failed logs, or all logs if showAll is true + if (showAll || file.failed) { + console.error(`\n========== ${file.step} ==========`); + try { + const content = fs.readFileSync(file.path, 'utf8'); + console.error(content); + } catch (err) { + console.error(`Error reading log file: ${err.message}`); + } + } + }); + /* eslint-enable no-console */ +} + const tasks = new Listr([ { title: `Change working dir`, @@ -17,34 +52,118 @@ const tasks = new Listr([ }, { title: `Build website's all parts`, - task: () => Promise.allSettled([ - exec('yarn run build:blog:zh', { stdio: 'ignore' }), - exec('yarn run build:blog:en', { stdio: 'ignore' }), - exec('yarn run build:doc', { stdio: 'ignore' }), - exec('yarn run build:website', { stdio: 'ignore' }), - ]), + task: async (ctx) => { + const buildSteps = [ + { name: 'blog-zh', cmd: 'yarn run build:blog:zh' }, + { name: 'blog-en', cmd: 'yarn run build:blog:en' }, + { name: 'doc', cmd: 'yarn run build:doc' }, + { name: 'website', cmd: 'yarn run build:website' }, + ]; + + // Execute all builds in parallel, capturing output + const results = await Promise.allSettled( + buildSteps.map((step) => exec(step.cmd)), + ); + + // Check for failures and save logs to files + const failures = []; + const logFiles = []; + + results.forEach((result, index) => { + const step = buildSteps[index]; + const logFilePath = path.join(logDir, `${step.name}.log`); + let logContent = ''; + + if (result.status === 'fulfilled') { + // Write raw stdout and stderr without extra formatting + if (result.value.stdout) { + logContent += result.value.stdout; + } + if (result.value.stderr) { + if (logContent) logContent += '\n'; + logContent += result.value.stderr; + } + } else { + failures.push(step.name); + // Write raw stdout and stderr for failed builds + if (result.reason.stdout) { + logContent += result.reason.stdout; + } + if (result.reason.stderr) { + if (logContent) logContent += '\n'; + logContent += result.reason.stderr; + } + // Add error message at the end if available + if (result.reason.message && !result.reason.stderr?.includes(result.reason.message)) { + if (logContent) logContent += '\n'; + logContent += `Error: ${result.reason.message}\n`; + } + } + + // Write to log file + fs.writeFileSync(logFilePath, logContent); + logFiles.push({ path: logFilePath, step: step.name, failed: result.status === 'rejected' }); + }); + + // Store in context for later use + ctx.buildLogFiles = logFiles; + ctx.buildFailures = failures; + + // If any build failed, throw error + if (failures.length > 0) { + throw new Error(`Build failed for: ${failures.join(', ')}`); + } + }, }, { title: `Copy website's all parts to website's root`, task: () => Promise.allSettled([ exec( 'cp ./.asf.yaml ./.htaccess ./blog/en/build/blog ./blog/en/build/assets ./doc/build/assets ./doc/build/docs ./website/build/ -r', - { stdio: 'ignore' }, ), exec( 'cp ./blog/zh/build/blog ./blog/zh/build/assets ./doc/build/zh/docs ./doc/build/zh/assets ./website/build/zh/ -r', - { stdio: 'ignore' }, ), ]), }, -]); +], { + renderer: isCI ? 'verbose' : 'default', +}); tasks .run() .then(() => { + /* eslint-disable-next-line no-console */ console.log(`[Finish] Generate website`); + + // In local environment, show log location after successful build + if (!isCI && showLogsLocally && fs.existsSync(logDir)) { + /* eslint-disable-next-line no-console */ + console.log(`\nBuild logs: ${logDir}/*.log`); + } }) .catch((err) => { + /* eslint-disable-next-line no-console */ console.error(err); + + // Print information about log files and their content + if (err.context && err.context.buildLogFiles) { + /* eslint-disable no-console */ + console.error(`\nBuild logs saved to: ${logDir}`); + err.context.buildLogFiles.forEach((file) => { + const status = file.failed ? '❌' : '✓'; + console.error(` ${status} ${file.step}.log`); + }); + /* eslint-enable no-console */ + + // In local environment, automatically print failed logs + if (!isCI && showLogsLocally) { + printLogFiles(err.context.buildLogFiles, false); + } else if (!isCI) { + /* eslint-disable-next-line no-console */ + console.error(`\nView logs: cat ${logDir}/*.log`); + } + } + process.exit(1); });
