Persist fallback audit targets before use and fail upload starts or active-batch additions closed when the plan cannot be recorded. Keep lifecycle audits out of session and debug logs, expose diagnostics through opaque metadata, localize audit failures, and redact paths plus complete credential values without corrupting benign text.
91 lines
4.1 KiB
JavaScript
91 lines
4.1 KiB
JavaScript
const nodePath = require('path');
|
|
const { formatUploadPlanLogLine } = require('./upload-log');
|
|
|
|
function getUploadAuditLogPath(uploadLogPath, pathApi = nodePath) {
|
|
if (typeof uploadLogPath !== 'string' || !uploadLogPath.trim()) return null;
|
|
return pathApi.join(pathApi.dirname(uploadLogPath), 'upload-audit.log');
|
|
}
|
|
|
|
function createUploadAuditWriter(options) {
|
|
const source = options && typeof options === 'object' ? options : {};
|
|
const fs = source.fs;
|
|
const path = source.path || nodePath;
|
|
const resolveUploadLogTarget = source.resolveUploadLogTarget;
|
|
const rotateLogFile = typeof source.rotateLogFile === 'function' ? source.rotateLogFile : () => {};
|
|
const invalidateUploadLogTarget = typeof source.invalidateUploadLogTarget === 'function' ? source.invalidateUploadLogTarget : () => {};
|
|
const persistFallbackLogPath = typeof source.persistFallbackLogPath === 'function' ? source.persistFallbackLogPath : async () => false;
|
|
const reportError = typeof source.reportError === 'function' ? source.reportError : () => {};
|
|
const retryDelays = Array.isArray(source.retryDelays) && source.retryDelays.length > 0 ? source.retryDelays : [0, 100, 250];
|
|
const maxBytes = Number.isFinite(source.maxBytes) ? source.maxBytes : 10 * 1024 * 1024;
|
|
const maxBackups = Number.isFinite(source.maxBackups) ? source.maxBackups : 2;
|
|
let activePath = null;
|
|
|
|
if (!fs || !fs.promises || typeof fs.promises.appendFile !== 'function' || typeof resolveUploadLogTarget !== 'function') {
|
|
throw new TypeError('createUploadAuditWriter requires fs and resolveUploadLogTarget');
|
|
}
|
|
|
|
async function append(line, label) {
|
|
const excludedPaths = new Set();
|
|
for (const delay of retryDelays) {
|
|
if (delay) await new Promise(resolve => setTimeout(resolve, delay));
|
|
const uploadTarget = resolveUploadLogTarget(excludedPaths);
|
|
if (!uploadTarget || excludedPaths.has(uploadTarget.path)) continue;
|
|
const targetPath = uploadTarget && getUploadAuditLogPath(uploadTarget.path, path);
|
|
if (!targetPath) continue;
|
|
try {
|
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
if (uploadTarget.isFallback) {
|
|
let persisted = false;
|
|
try {
|
|
persisted = await persistFallbackLogPath(uploadTarget.path);
|
|
} catch (error) {
|
|
reportError('audit-fallback-persist', error);
|
|
}
|
|
if (persisted !== true) {
|
|
excludedPaths.add(uploadTarget.path);
|
|
invalidateUploadLogTarget();
|
|
reportError('audit-fallback-persist', new Error('Fallback log path could not be persisted'));
|
|
continue;
|
|
}
|
|
}
|
|
rotateLogFile(targetPath, maxBytes, maxBackups);
|
|
await fs.promises.appendFile(targetPath, line, 'utf-8');
|
|
activePath = targetPath;
|
|
return true;
|
|
} catch (error) {
|
|
excludedPaths.add(uploadTarget.path);
|
|
invalidateUploadLogTarget();
|
|
reportError(label, error);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return { append, getActivePath: () => activePath };
|
|
}
|
|
|
|
function createUploadAuditEvents(writer, now = () => new Date()) {
|
|
if (!writer || typeof writer.append !== 'function') throw new TypeError('createUploadAuditEvents requires an audit writer');
|
|
return {
|
|
appendSourceCleanup: event => writer.append(`# SOURCE-CLEANUP ${JSON.stringify(event)}\r\n`, 'source-cleanup'),
|
|
appendUploadPlan: (plan, mode) => writer.append(formatUploadPlanLogLine(now(), plan, mode), 'upload-plan')
|
|
};
|
|
}
|
|
|
|
async function runAfterDurableAudit(audit, action) {
|
|
let persisted = false;
|
|
try {
|
|
persisted = await audit();
|
|
} catch {}
|
|
if (persisted !== true) return { ok: false };
|
|
return { ok: true, value: await action() };
|
|
}
|
|
|
|
function getUploadAuditFailureMessage(language) {
|
|
return language === 'de'
|
|
? 'Der Uploadplan konnte nicht dauerhaft protokolliert werden. Bitte prüfe den Log-Pfad und versuche es erneut.'
|
|
: 'The upload plan could not be recorded durably. Check the log path and try again.';
|
|
}
|
|
|
|
module.exports = { getUploadAuditLogPath, createUploadAuditWriter, createUploadAuditEvents, runAfterDurableAudit, getUploadAuditFailureMessage };
|