1- import { EventEmitter } from 'node:events'
21import { randomUUID } from 'node:crypto'
3- import type { Storage } from './storage/types.ts'
2+ import { EventEmitter } from 'node:events'
3+ import type { Logger } from 'pino'
44import type { Serde } from './serde/index.ts'
5- import type { QueueMessage } from './types.ts'
65import { createJsonSerde } from './serde/index.ts'
6+ import type { Storage } from './storage/types.ts'
7+ import type { QueueMessage } from './types.ts'
8+ import { abstractLogger , ensureLoggableError } from './utils/logging.ts'
79import { parseState } from './utils/state.ts'
810
911interface LeaderElectionConfig {
@@ -18,6 +20,7 @@ interface ReaperConfig<TPayload> {
1820 payloadSerde ?: Serde < TPayload >
1921 visibilityTimeout ?: number
2022 leaderElection ?: LeaderElectionConfig
23+ logger ?: Logger
2124}
2225
2326interface ReaperEvents {
@@ -46,6 +49,7 @@ export class Reaper<TPayload> extends EventEmitter<ReaperEvents> {
4649 #payloadSerde: Serde < TPayload >
4750 #visibilityTimeout: number
4851 #leaderElection: LeaderElectionConfig
52+ #logger: Logger
4953
5054 #running = false
5155 #unsubscribe: ( ( ) => Promise < void > ) | null = null
@@ -63,6 +67,7 @@ export class Reaper<TPayload> extends EventEmitter<ReaperEvents> {
6367 this . #visibilityTimeout = config . visibilityTimeout ?? 30000
6468 this . #leaderElection = config . leaderElection ?? { enabled : false }
6569 this . #reaperId = randomUUID ( )
70+ this . #logger = ( config . logger ?? abstractLogger ) . child ( { component : 'reaper' , reaperId : this . #reaperId } )
6671 }
6772
6873 /**
@@ -201,7 +206,7 @@ export class Reaper<TPayload> extends EventEmitter<ReaperEvents> {
201206 }
202207 }
203208 } catch ( err ) {
204- this . emit ( 'error' , err as Error )
209+ this . #emitError ( err , 'Leadership check failed.' )
205210 }
206211 } , interval )
207212 }
@@ -212,7 +217,7 @@ export class Reaper<TPayload> extends EventEmitter<ReaperEvents> {
212217 async #tryAcquireLock ( ttlMs : number ) : Promise < boolean > {
213218 if ( ! this . #storage. acquireLeaderLock ) {
214219 // Storage doesn't support leader election
215- this . emit ( 'error' , new Error ( 'Storage does not support leader election' ) )
220+ this . #emitError ( new Error ( 'Storage does not support leader election' ) )
216221 return false
217222 }
218223
@@ -294,7 +299,7 @@ export class Reaper<TPayload> extends EventEmitter<ReaperEvents> {
294299 const timer = setTimeout ( ( ) => {
295300 this . #processingTimers. delete ( id )
296301 this . #checkJob( id ) . catch ( err => {
297- this . emit ( 'error' , err )
302+ this . #emitError ( err , 'Failed checking job after visibility timer.' )
298303 } )
299304 } , this . #visibilityTimeout)
300305
@@ -337,7 +342,7 @@ export class Reaper<TPayload> extends EventEmitter<ReaperEvents> {
337342 const timer = setTimeout ( ( ) => {
338343 this . #processingTimers. delete ( id )
339344 this . #checkJob( id ) . catch ( err => {
340- this . emit ( 'error' , err )
345+ this . #emitError ( err , 'Failed re-checking job after visibility timeout.' )
341346 } )
342347 } , remaining )
343348 this . #processingTimers. set ( id , timer )
@@ -353,7 +358,7 @@ export class Reaper<TPayload> extends EventEmitter<ReaperEvents> {
353358 */
354359 async #recoverStalledJob ( id : string , workerId ?: string ) : Promise < void > {
355360 if ( ! workerId ) {
356- this . emit ( 'error' , new Error ( `Cannot recover stalled job ${ id } : no workerId in state` ) )
361+ this . #emitError ( new Error ( `Cannot recover stalled job ${ id } : no workerId in state` ) )
357362 return
358363 }
359364
@@ -435,7 +440,7 @@ export class Reaper<TPayload> extends EventEmitter<ReaperEvents> {
435440 const timer = setTimeout ( ( ) => {
436441 this . #processingTimers. delete ( queueMessage . id )
437442 this . #checkJob( queueMessage . id ) . catch ( err => {
438- this . emit ( 'error' , err )
443+ this . #emitError ( err , 'Failed checking worker processing job.' )
439444 } )
440445 } , remaining )
441446 this . #processingTimers. set ( queueMessage . id , timer )
@@ -446,4 +451,15 @@ export class Reaper<TPayload> extends EventEmitter<ReaperEvents> {
446451 }
447452 }
448453 }
454+
455+ #emitError ( err : unknown , message = 'Reaper emitted error.' ) : void {
456+ const error = err instanceof Error ? err : new Error ( String ( err ) )
457+
458+ if ( this . listenerCount ( 'error' ) > 0 ) {
459+ this . emit ( 'error' , error )
460+ return
461+ }
462+
463+ this . #logger. error ( { err : ensureLoggableError ( error ) } , message )
464+ }
449465}
0 commit comments