fix: Make QA metrics telemetry truly fire-and-forget (#32597)

Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
n8n-cat-bot[bot]
2026-06-18 17:36:30 +01:00
committed by GitHub
parent a70eb97822
commit 6e8a7fcd2d
4 changed files with 42 additions and 22 deletions
+3 -2
View File
@@ -101,9 +101,10 @@ GROUP BY 1, 2 ORDER BY 1;
```javascript
import { sendMetrics, metric } from './send-metrics.mjs';
await sendMetrics([
// Fire-and-forget — best-effort telemetry, must never block CI.
sendMetrics([
metric('my-metric', 42.0, 'ms', { context: 'value' }),
]);
]).catch((err) => console.warn(`[metrics] send failed: ${err.message}`));
```
**From a Playwright test:**
+7 -1
View File
@@ -70,4 +70,10 @@ metrics.push(
}),
);
await sendMetrics(metrics, 'build-stats');
// Fire-and-forget: don't await. The in-flight fetch is cancelled on process
// exit, which is the right trade-off — we drop a data point rather than block
// CI on a slow webhook. sendMetrics swallows its own errors, but attach a
// .catch defensively in case that ever changes.
sendMetrics(metrics, 'build-stats').catch((err) =>
console.warn(`[metrics] send failed: ${err.message}`),
);
+4 -1
View File
@@ -85,4 +85,7 @@ if (metrics.length === 0) {
process.exit(0);
}
await sendMetrics(metrics, 'docker-stats');
// Fire-and-forget: see send-build-stats.mjs for rationale.
sendMetrics(metrics, 'docker-stats').catch((err) =>
console.warn(`[metrics] send failed: ${err.message}`),
);
+28 -18
View File
@@ -3,9 +3,10 @@
* Shared metrics sender for CI scripts.
* See .github/CI-TELEMETRY.md for payload shape and BigQuery schema.
*
* Usage:
* Usage (fire-and-forget — best-effort, never blocks CI):
* import { sendMetrics, metric } from './send-metrics.mjs';
* await sendMetrics([metric('build-duration', 45.2, 's', { package: '@n8n/cli' })]);
* sendMetrics([metric('build-duration', 45.2, 's', { package: '@n8n/cli' })])
* .catch((err) => console.warn(`[metrics] send failed: ${err.message}`));
*
* Env: QA_METRICS_WEBHOOK_URL, QA_METRICS_WEBHOOK_USER, QA_METRICS_WEBHOOK_PASSWORD
*/
@@ -78,22 +79,31 @@ export async function sendMetrics(metrics, benchmarkName = null) {
const payload = { ...buildContext(benchmarkName), metrics };
const basicAuth = Buffer.from(`${webhookUser}:${webhookPassword}`).toString('base64');
const response = await fetch(webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${basicAuth}`,
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(30_000),
});
// Best-effort telemetry: never throw into the caller. Slow/down webhook
// becomes a one-line warning instead of an uncaught rejection. 5s caps
// wall-time in callers that do await this; fire-and-forget callers exit
// before then anyway.
try {
const response = await fetch(webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${basicAuth}`,
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(5_000),
});
if (!response.ok) {
const body = await response.text().catch(() => '');
throw new Error(
`Webhook failed: ${response.status} ${response.statusText}${body ? `\n${body}` : ''}`,
);
if (!response.ok) {
const body = await response.text().catch(() => '');
console.warn(
`[metrics] webhook ${response.status} ${response.statusText}${body ? `\n${body}` : ''}`,
);
return;
}
console.log(`Sent ${metrics.length} metric(s): ${response.status}`);
} catch (err) {
console.warn(`[metrics] send failed (non-fatal): ${err.message}`);
}
console.log(`Sent ${metrics.length} metric(s): ${response.status}`);
}