feat: add privacy-preserving telemetry and error aggregation

- Update smoke-test.mjs to output structured summary with logs
- Update telemetry-aggregator.mjs to collect from unit, smoke, and load tests
- Update run-unit-tests.mjs to log output to artifacts/test_unit.log
- Ensure PII sanitization in telemetry reports
This commit is contained in:
google-labs-jules[bot] committed 2026-01-26 06:48:14 +00:00
1 parent 87c494c1f3
commit edbd66df52
3 files changed
+92 -6

No files matched your search

+6 -2
View File
@@ -38,7 +38,7 @@ import http from 'http';
const ARTIFACTS_DIR = 'artifacts';
const LOG_FILE = path.join(ARTIFACTS_DIR, `smoke-${new Date().toISOString().split('T')[0].replace(/-/g, '')}.log`);
const REPORT_FILE = path.join(ARTIFACTS_DIR, 'smoke-report.json');
const REPORT_FILE = path.join(ARTIFACTS_DIR, `smoke-summary-${new Date().toISOString().replace(/[:.]/g, '-')}.json`);
const UI_SCREENSHOT = path.join(ARTIFACTS_DIR, 'smoke-ui.png');
if (!fs.existsSync(ARTIFACTS_DIR)) {
@@ -285,12 +285,16 @@ async function runSmokeTest() {
} catch (error) {
log(`Smoke Test Failed: ${error.stack}`, 'ERROR');
summary.status = "FAIL";
summary.status = "failure";
summary.error = error.message;
exitCode = 1;
} finally {
// --- Write Report ---
try {
if (fs.existsSync(LOG_FILE)) {
const logs = fs.readFileSync(LOG_FILE, 'utf8').split('\n').filter(Boolean);
summary.logs = logs;
}
fs.writeFileSync(REPORT_FILE, JSON.stringify(summary, null, 2));
log(`Summary written to ${REPORT_FILE}`);
} catch (e) {
+70 -3
View File
@@ -61,9 +61,13 @@ function suggestOwner(stack) {
function collectUnitTests(logFile = 'test_output.log') {
const errors = [];
if (!fs.existsSync(logFile)) return errors;
// Check root and artifacts dir
const potentialPaths = [logFile, path.join(ARTIFACTS_DIR, logFile)];
const filepath = potentialPaths.find(p => fs.existsSync(p));
const content = fs.readFileSync(logFile, 'utf8');
if (!filepath) return errors;
const content = fs.readFileSync(filepath, 'utf8');
const lines = content.split('\n');
let currentError = null;
let insideErrorBlock = false;
@@ -182,7 +186,7 @@ function collectSmokeTests() {
const content = JSON.parse(fs.readFileSync(path.join(ARTIFACTS_DIR, file), 'utf8'));
// Expected format: { timestamp, status, error, logs }
if (content.status === 'failure') {
if (content.status && (content.status.toLowerCase() === 'failure' || content.status.toLowerCase() === 'fail')) {
const errorMsg = content.error || 'Unknown Smoke Test Failure';
const sanitizedError = sanitize(errorMsg);
@@ -258,6 +262,62 @@ function collectFuzzReports() {
return errors;
}
function collectLoadTests() {
const errors = [];
if (!fs.existsSync(ARTIFACTS_DIR)) return errors;
const files = fs.readdirSync(ARTIFACTS_DIR).filter(f => f.startsWith('load-report-') && f.endsWith('.json'));
// Also check load-report.json
if (fs.existsSync(path.join(ARTIFACTS_DIR, 'load-report.json'))) {
files.push('load-report.json');
}
const uniqueFiles = [...new Set(files)];
for (const file of uniqueFiles) {
try {
const content = JSON.parse(fs.readFileSync(path.join(ARTIFACTS_DIR, file), 'utf8'));
// Check for explicit errors
if (content.metrics && content.metrics.errors > 0) {
const errorBreakdown = content.metrics.error_breakdown ? JSON.stringify(content.metrics.error_breakdown, null, 2) : 'No breakdown';
const sanitizedBreakdown = sanitize(errorBreakdown);
errors.push({
source: `load-test:${file}`,
title: `Load Test Errors: ${content.metrics.errors} failures`,
details: [sanitizedBreakdown],
stack: `Load Test Errors:\n${sanitizedBreakdown}`,
fingerprint: getFingerprint(`load-test-errors-${sanitizedBreakdown}`),
severity: 'High',
owner: 'P2P Team'
});
}
// Check for bottlenecks
if (content.bottlenecks && content.bottlenecks.length > 0) {
for (let i = 0; i < content.bottlenecks.length; i++) {
const bn = content.bottlenecks[i];
const rem = content.remediation ? content.remediation[i] : '';
const sanitizedBn = sanitize(bn);
errors.push({
source: `load-test:${file}`,
title: `Load Test Bottleneck: ${sanitizedBn}`,
details: [sanitize(rem)],
stack: `Bottleneck: ${sanitizedBn}\nRemediation: ${sanitize(rem)}`,
fingerprint: getFingerprint(`load-test-bottleneck-${sanitizedBn}`),
severity: 'Medium',
owner: 'P2P Team'
});
}
}
} catch (err) {
console.warn(`Failed to parse load report ${file}:`, err.message);
}
}
return errors;
}
function collectAgentLogs() {
const errors = [];
if (!fs.existsSync(ARTIFACTS_DIR)) return errors;
@@ -419,6 +479,13 @@ async function main() {
errors.push(...smokeTestErrors);
}
// Load Tests
const loadTestErrors = collectLoadTests();
if (loadTestErrors.length > 0) {
console.log(`Collected ${loadTestErrors.length} load test errors.`);
errors.push(...loadTestErrors);
}
// Fuzz Reports
const fuzzErrors = collectFuzzReports();
if (fuzzErrors.length > 0) {
+16 -1
View File
@@ -1,5 +1,6 @@
import { spawn } from "node:child_process";
import { readdir, readFile } from "node:fs/promises";
import fs from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";
@@ -101,14 +102,28 @@ async function run() {
return;
}
const logFile = path.join(rootDir, "artifacts", "test_unit.log");
if (!fs.existsSync(path.dirname(logFile))) {
fs.mkdirSync(path.dirname(logFile), { recursive: true });
}
const logStream = fs.createWriteStream(logFile);
for (const testFile of selectedTests) {
const relativePath = path.relative(rootDir, testFile);
console.log(`\n→ Running ${relativePath}`);
logStream.write(`\n→ Running ${relativePath}\n`);
await new Promise((resolve, reject) => {
const child = spawn(process.execPath, ["--import", setupImport.href, testFile], {
stdio: "inherit",
stdio: ["inherit", "pipe", "pipe"],
});
child.stdout.pipe(process.stdout, { end: false });
child.stdout.pipe(logStream, { end: false });
child.stderr.pipe(process.stderr, { end: false });
child.stderr.pipe(logStream, { end: false });
let finished = false;
let timeoutId = null;