feat(audit): implement audit logging for search requests and results
All checks were successful
Publish FaceAI Container / publish (push) Successful in 13m22s

- Added configuration options for audit database path and retention days in backend and processor.
- Integrated audit logging in server and worker processes to track search requests, completions, and failures.
- Created utility functions for reading and parsing audit logs in end-to-end tests.
- Updated Docker Compose files to include audit database configuration.
- Added new tests to verify audit log entries for successful and no-results searches.
This commit is contained in:
MaddoScientisto 2026-05-19 23:29:38 +02:00
commit 32db61c381
14 changed files with 1067 additions and 16 deletions

View file

@ -26,6 +26,8 @@ export const config = {
workerTimeoutMs: Number(process.env.FACEAI_WORKER_TIMEOUT_MS || 5 * 60 * 1000),
runtimeRoot: process.env.FACEAI_RUNTIME_ROOT || '/data/runtime',
logRoot: process.env.FACEAI_LOG_ROOT || path.join(process.env.FACEAI_RUNTIME_ROOT || '/data/runtime', 'logs'),
auditDbPath: process.env.FACEAI_AUDIT_DB_PATH || path.join(process.env.FACEAI_LOG_ROOT || '/data/logs', 'faceai-audit.sqlite'),
auditRetentionDays: Number(process.env.FACEAI_AUDIT_RETENTION_DAYS || 730),
pklRoot: process.env.FACEAI_PKL_ROOT || '/data/pkl',
matcherBinary: process.env.FACEAI_MATCHER_BINARY || '/app/bin/face_matcher',
matcherTolerance,

View file

@ -32,7 +32,7 @@ export async function runFaceMatcher({ matcherBinary, matcherTolerance, selfiePa
];
if (matcherTolerance !== null && matcherTolerance !== undefined) {
matcherArgs.push('--tollerance', String(matcherTolerance));
matcherArgs.push('--tolerance', String(matcherTolerance));
}
const child = spawn(matcherBinary, matcherArgs, {

View file

@ -3,6 +3,7 @@ import fs from 'node:fs/promises';
import path from 'node:path';
import { Worker } from 'bullmq';
import { config } from './config.js';
import { createAuditStore } from '../../backend/src/audit-store.js';
import {
createRedisConnection,
getSearchRecord,
@ -16,6 +17,7 @@ import {
import { parseMatcherCsv, resolvePklPath, runFaceMatcher } from './worker-utils.js';
const connection = createRedisConnection(config.redisUrl);
const auditStore = createAuditStore({ dbPath: config.auditDbPath, retentionDays: config.auditRetentionDays });
async function ensureMatcherBinaryAvailable() {
try {
@ -93,9 +95,23 @@ async function completeSearch(search, searchId, searchLogPath, matchCount, match
completionCode
});
await markSearchCompleted(connection, searchId, result.id, matchCount, config.searchTtlSeconds, {
const completedSearch = await markSearchCompleted(connection, searchId, result.id, matchCount, config.searchTtlSeconds, {
completionCode
});
auditStore.markSearchCompleted({
searchId,
user: { id: search.userId },
race: {
id: search.raceId,
name: search.raceName,
storage: search.raceStorage
},
resultId: result.id,
matchCount,
matches,
completionCode,
completedAt: completedSearch?.completedAt || Date.now()
});
await releaseActiveSearchLock(connection, search.userId, searchId);
}
@ -178,7 +194,22 @@ async function processJob(job) {
message: error.message,
stack: error.stack || null
});
await markSearchFailed(connection, searchId, 'PROCESSOR_ERROR', error.message, config.searchTtlSeconds);
const failedSearch = await markSearchFailed(connection, searchId, 'PROCESSOR_ERROR', error.message, config.searchTtlSeconds);
auditStore.markSearchFailed({
searchId,
user: { id: search.userId },
race: {
id: search.raceId,
name: search.raceName,
storage: search.raceStorage
},
errorCode: 'PROCESSOR_ERROR',
errorMessage: error.message,
completedAt: failedSearch?.completedAt || Date.now(),
payload: {
stack: error.stack || null
}
});
await releaseActiveSearchLock(connection, search.userId, searchId);
throw error;
}