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:
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user