mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-29 17:02:28 +00:00
Implement privacy-preserving telemetry aggregator script
- Update `scripts/telemetry-aggregator.mjs` to dynamically discover `test_unit*.log` files and include `server.log`. - Enhance error collection to handle both TAP failures and generic JS crashes/errors. - Implement robust PII sanitization for IPs, emails, hex keys, and local paths. - Aggregate errors by stack trace fingerprint and generate a Markdown report for triage. - Ensure the process is opt-in (via manual script execution) and artifacts are local-only.
This commit is contained in:
1 parent
870df0482a
commit
7bfc00105d
1 file changed
+43
-24
@@ -5,7 +5,8 @@ import crypto from 'node:crypto';
|
||||
// Configuration
|
||||
const ARTIFACTS_DIR = 'artifacts';
|
||||
const REPORTS_DIR = 'ai/reports';
|
||||
const LOG_FILES = ['test_output.log', 'server.log', 'python_server.log', 'npm_output.log'];
|
||||
// We will dynamically discover log files in main(), but these are explicit targets if found.
|
||||
const EXPLICIT_LOG_FILES = ['python_server.log', 'server.log', 'npm_output.log'];
|
||||
|
||||
// Regex patterns for PII sanitization
|
||||
const PII_PATTERNS = [
|
||||
@@ -56,11 +57,11 @@ function suggestOwner(stack) {
|
||||
// Collectors
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
function collectUnitTests() {
|
||||
function collectUnitTests(logFile) {
|
||||
const errors = [];
|
||||
const logFile = 'test_output.log';
|
||||
if (!fs.existsSync(logFile)) return errors;
|
||||
|
||||
console.log(`Parsing unit test log: ${logFile}`);
|
||||
const content = fs.readFileSync(logFile, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
let currentError = null;
|
||||
@@ -76,7 +77,7 @@ function collectUnitTests() {
|
||||
}
|
||||
const title = line.replace(/^not ok \d+ - /, '').trim();
|
||||
currentError = {
|
||||
source: 'unit-test',
|
||||
source: `unit-test:${path.basename(logFile)}`,
|
||||
title: sanitize(title),
|
||||
details: [],
|
||||
severity: 'High'
|
||||
@@ -85,18 +86,36 @@ function collectUnitTests() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Detect generic JS errors in output that aren't strictly TAP failures but cause crashes
|
||||
if (line.match(/^Error:/) || line.match(/^TypeError:/) || line.match(/^ReferenceError:/)) {
|
||||
// If we are already inside a TAP failure block, this is likely detail
|
||||
if (insideErrorBlock && currentError) {
|
||||
currentError.details.push(sanitize(line));
|
||||
} else {
|
||||
// Independent error (crash?)
|
||||
if (currentError) {
|
||||
errors.push(currentError);
|
||||
}
|
||||
currentError = {
|
||||
source: `unit-test-crash:${path.basename(logFile)}`,
|
||||
title: sanitize(line.trim()),
|
||||
details: [sanitize(line.trim())],
|
||||
severity: 'Critical'
|
||||
};
|
||||
insideErrorBlock = true; // Treat as error block
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// End of failure block detection (heuristic)
|
||||
// TAP ok line, or start of a new test section, or unexpected unindented line that looks like a header
|
||||
if (insideErrorBlock) {
|
||||
if (line.match(/^(ok \d+|# tests \d+|1\.\.\d+|TAP version)/)) {
|
||||
// TAP ok line, or start of a new test section (Running tests/...), or TAP version
|
||||
if (line.match(/^(ok \d+|# tests \d+|1\.\.\d+|TAP version|→ Running )/)) {
|
||||
insideErrorBlock = false;
|
||||
errors.push(currentError);
|
||||
currentError = null;
|
||||
} else {
|
||||
// Capture detail lines
|
||||
// If line is empty or indented, it's likely part of the error
|
||||
// If it's unindented but not a TAP command, it might still be part of it (console logs)
|
||||
// We'll be greedy here.
|
||||
currentError.details.push(sanitize(line));
|
||||
}
|
||||
}
|
||||
@@ -110,7 +129,7 @@ function collectUnitTests() {
|
||||
errors.forEach(e => {
|
||||
const fullText = e.details.join('\n');
|
||||
// Try to extract stack trace
|
||||
const stackMatch = fullText.match(/stack: \|-([\s\S]+?)(?:\n\s*\w+:|$)/) || fullText.match(/Error:([\s\S]+)/);
|
||||
const stackMatch = fullText.match(/stack: \|-([\s\S]+?)(?:\n\s*\w+:|$)/) || fullText.match(/(?:Error|Exception):([\s\S]+)/);
|
||||
if (stackMatch) {
|
||||
e.stack = stackMatch[1].trim();
|
||||
} else {
|
||||
@@ -132,11 +151,9 @@ function collectGenericLogs(filepath, sourceName) {
|
||||
let currentError = null;
|
||||
|
||||
for (const line of lines) {
|
||||
// Simple heuristic for generic logs: look for "Error:" or "Exception:"
|
||||
// and capture following lines that look like stack traces (start with 'at ' or are indented)
|
||||
|
||||
const isErrorStart = /Error:|Exception:|FAIL|CRITICAL/.test(line);
|
||||
const isStackLine = /^\s+at /.test(line) || /^\s+/.test(line); // Indented lines often follow errors
|
||||
// Indented lines or lines starting with 'at ' often follow errors
|
||||
const isStackLine = /^\s+at /.test(line) || /^\s+/.test(line);
|
||||
|
||||
if (isErrorStart) {
|
||||
if (currentError) {
|
||||
@@ -151,7 +168,7 @@ function collectGenericLogs(filepath, sourceName) {
|
||||
} else if (currentError && isStackLine) {
|
||||
currentError.details.push(sanitize(line.trim()));
|
||||
} else if (currentError) {
|
||||
// End of error block
|
||||
// End of error block if we hit a non-indented line
|
||||
errors.push(currentError);
|
||||
currentError = null;
|
||||
}
|
||||
@@ -239,8 +256,6 @@ function aggregate(errors) {
|
||||
grouped[key].sources.add(err.source);
|
||||
// Escalating severity if we see critical
|
||||
if (err.severity === 'Critical') grouped[key].severity = 'Critical';
|
||||
|
||||
// Update title if new one is longer/better (optional, keeping first for now)
|
||||
}
|
||||
|
||||
return Object.values(grouped).sort((a, b) => {
|
||||
@@ -331,14 +346,18 @@ async function main() {
|
||||
// 1. Collect
|
||||
const errors = [];
|
||||
|
||||
// Unit Tests
|
||||
const unitTestErrors = collectUnitTests();
|
||||
console.log(`Collected ${unitTestErrors.length} unit test errors.`);
|
||||
errors.push(...unitTestErrors);
|
||||
// Unit Tests - Dynamically find test_unit*.log
|
||||
const files = fs.readdirSync('.');
|
||||
const testLogs = files.filter(f => f.startsWith('test_unit') && f.endsWith('.log'));
|
||||
|
||||
// Server Logs
|
||||
for (const logFile of LOG_FILES) {
|
||||
if (logFile === 'test_output.log') continue; // Handled by collectUnitTests
|
||||
for (const logFile of testLogs) {
|
||||
const unitTestErrors = collectUnitTests(logFile);
|
||||
console.log(`Collected ${unitTestErrors.length} unit test errors from ${logFile}.`);
|
||||
errors.push(...unitTestErrors);
|
||||
}
|
||||
|
||||
// Explicit generic logs
|
||||
for (const logFile of EXPLICIT_LOG_FILES) {
|
||||
const genericErrors = collectGenericLogs(logFile, logFile);
|
||||
console.log(`Collected ${genericErrors.length} errors from ${logFile}.`);
|
||||
errors.push(...genericErrors);
|
||||
|
||||
Reference in new issue
Block a user