feat(03-02): instrument processJob with fire-and-forget analytics events

- Add import for recordProcessingEvent from analyticsService
- Emit upload_started after markAsProcessing (job processing start)
- Emit completed with duration_ms after markAsCompleted (job success)
- Emit failed with duration_ms and error_message in catch block (job failure)
- All calls are void/fire-and-forget (no await) per PITFALL-6 constraint
- Null-guard on job in catch block prevents runtime errors
This commit is contained in:
admin
2026-02-24 15:44:12 -05:00
parent 301d0bf159
commit dabd4a5ecf

View File

@@ -3,6 +3,7 @@ import { ProcessingJobModel, ProcessingJob } from '../models/ProcessingJobModel'
import { DocumentModel } from '../models/DocumentModel'; import { DocumentModel } from '../models/DocumentModel';
import { fileStorageService } from './fileStorageService'; import { fileStorageService } from './fileStorageService';
import { unifiedDocumentProcessor } from './unifiedDocumentProcessor'; import { unifiedDocumentProcessor } from './unifiedDocumentProcessor';
import { recordProcessingEvent } from './analyticsService';
export class JobProcessorService { export class JobProcessorService {
private isProcessing = false; private isProcessing = false;
@@ -133,6 +134,13 @@ export class JobProcessorService {
await ProcessingJobModel.markAsProcessing(jobId); await ProcessingJobModel.markAsProcessing(jobId);
jobStatusUpdated = true; // Track that we've updated status jobStatusUpdated = true; // Track that we've updated status
// Analytics: job processing started (fire-and-forget, void return)
recordProcessingEvent({
document_id: job.document_id,
user_id: job.user_id,
event_type: 'upload_started',
});
// Add timeout protection (28 minutes for API immediate processing, 14 for scheduled function) // Add timeout protection (28 minutes for API immediate processing, 14 for scheduled function)
// API endpoint has 30-min Cloud Run timeout; scheduled function has 15 min // API endpoint has 30-min Cloud Run timeout; scheduled function has 15 min
const processingTimeout = 28 * 60 * 1000; // 28 minutes in milliseconds const processingTimeout = 28 * 60 * 1000; // 28 minutes in milliseconds
@@ -333,6 +341,14 @@ export class JobProcessorService {
processingTime, processingTime,
attempts: job.attempts + 1, attempts: job.attempts + 1,
}); });
// Analytics: job completed (fire-and-forget, void return)
recordProcessingEvent({
document_id: job.document_id,
user_id: job.user_id,
event_type: 'completed',
duration_ms: processingTime,
});
})(), })(),
timeoutPromise timeoutPromise
]); ]);
@@ -363,6 +379,18 @@ export class JobProcessorService {
attempts: job ? job.attempts + 1 : 'unknown', attempts: job ? job.attempts + 1 : 'unknown',
}); });
// Analytics: job failed (fire-and-forget, void return)
// Guard with job check — job is null if findById failed before assignment
if (job) {
recordProcessingEvent({
document_id: job.document_id,
user_id: job.user_id,
event_type: 'failed',
duration_ms: processingTime,
error_message: errorMessage,
});
}
// Mark job as failed (will auto-retry if attempts < max_attempts) // Mark job as failed (will auto-retry if attempts < max_attempts)
try { try {
await ProcessingJobModel.markAsFailed(jobId, errorMessage); await ProcessingJobModel.markAsFailed(jobId, errorMessage);