56 lines
2.3 KiB
JavaScript
56 lines
2.3 KiB
JavaScript
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
class JsonlDiagnosticLog {
|
|
constructor(directory, options = {}) {
|
|
this.directory = directory;
|
|
this.retentionMs = (options.retentionDays || 7) * 86400000;
|
|
this.maxBytes = options.maxBytes || 256 * 1024 * 1024;
|
|
this.includeCaptionText = options.includeCaptionText !== false;
|
|
fs.mkdirSync(directory, { recursive: true });
|
|
}
|
|
append(entry) {
|
|
const safe = sanitize({ timestamp: new Date().toISOString(), ...entry }, this.includeCaptionText);
|
|
fs.appendFileSync(this.fileForToday(), `${JSON.stringify(safe)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
}
|
|
cleanup(now = Date.now()) {
|
|
const files = this.files();
|
|
let removed = 0;
|
|
for (const file of files) {
|
|
if (now - file.mtimeMs > this.retentionMs) { fs.rmSync(file.path, { force: true }); removed += 1; }
|
|
}
|
|
const retained = this.files();
|
|
let total = retained.reduce((sum, file) => sum + file.size, 0);
|
|
for (const file of retained) {
|
|
if (total <= this.maxBytes) break;
|
|
fs.rmSync(file.path, { force: true });
|
|
total -= file.size;
|
|
removed += 1;
|
|
}
|
|
return { removed, bytes: total };
|
|
}
|
|
files() {
|
|
return fs.readdirSync(this.directory).filter((name) => /^transcription-\d{4}-\d{2}-\d{2}\.jsonl$/.test(name)).map((name) => {
|
|
const target = path.join(this.directory, name);
|
|
const stat = fs.statSync(target);
|
|
return { path: target, name, size: stat.size, mtimeMs: stat.mtimeMs };
|
|
}).sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
}
|
|
fileForToday() { return path.join(this.directory, `transcription-${new Date().toISOString().slice(0, 10)}.jsonl`); }
|
|
}
|
|
|
|
function sanitize(value, includeCaptionText) {
|
|
if (Buffer.isBuffer(value)) return "[binary omitted]";
|
|
if (Array.isArray(value)) return value.map((entry) => sanitize(entry, includeCaptionText));
|
|
if (!value || typeof value !== "object") return value;
|
|
const result = {};
|
|
for (const [key, child] of Object.entries(value)) {
|
|
if (/audio|pcm|credential|secret|token/i.test(key)) result[key] = "[redacted]";
|
|
else if (!includeCaptionText && /(?:stable|uncertain|caption)_text/i.test(key)) result[key] = "[caption text disabled]";
|
|
else result[key] = sanitize(child, includeCaptionText);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
module.exports = { JsonlDiagnosticLog, sanitize };
|