@@ -7,6 +7,7 @@ import { createRedisClient } from "@internal/redis";
77import { Logger } from "@trigger.dev/core/logger" ;
88import { LogicalReplicationClientError } from "./errors.js" ;
99import { PgoutputMessage , PgoutputParser , getPgoutputStartReplicationSQL } from "./pgoutput.js" ;
10+ import { startSpan , trace , Tracer } from "@internal/tracing" ;
1011
1112export interface LogicalReplicationClientOptions {
1213 /**
@@ -70,6 +71,8 @@ export interface LogicalReplicationClientOptions {
7071 * The actions to publish to the publication.
7172 */
7273 publicationActions ?: Array < "insert" | "update" | "delete" | "truncate" > ;
74+
75+ tracer ?: Tracer ;
7376}
7477
7578export type LogicalReplicationClientEvents = {
@@ -101,6 +104,7 @@ export class LogicalReplicationClient {
101104 private lastAckTimestamp : number = 0 ;
102105 private ackIntervalTimer : NodeJS . Timeout | null = null ;
103106 private _isStopped : boolean = false ;
107+ private _tracer : Tracer ;
104108
105109 public get lastLsn ( ) : string {
106110 return this . lastAcknowledgedLsn ?? "0/00000000" ;
@@ -113,6 +117,7 @@ export class LogicalReplicationClient {
113117 constructor ( options : LogicalReplicationClientOptions ) {
114118 this . options = options ;
115119 this . logger = options . logger ?? new Logger ( "LogicalReplicationClient" , "info" ) ;
120+ this . _tracer = options . tracer ?? trace . getTracer ( "logical-replication-client" ) ;
116121
117122 this . autoAcknowledge =
118123 typeof options . autoAcknowledge === "boolean" ? options . autoAcknowledge : true ;
@@ -145,54 +150,62 @@ export class LogicalReplicationClient {
145150 }
146151
147152 public async stop ( ) : Promise < this> {
148- if ( this . _isStopped ) return this ;
149- this . _isStopped = true ;
150- // Clean up leader lock heartbeat
151- if ( this . leaderLockHeartbeatTimer ) {
152- clearInterval ( this . leaderLockHeartbeatTimer ) ;
153- this . leaderLockHeartbeatTimer = null ;
154- }
155- // Clean up ack interval
156- if ( this . ackIntervalTimer ) {
157- clearInterval ( this . ackIntervalTimer ) ;
158- this . ackIntervalTimer = null ;
159- }
160- // Release leader lock if held
161- await this . #releaseLeaderLock( ) ;
153+ return await startSpan ( this . _tracer , "logical_replication_client.stop" , async ( span ) => {
154+ if ( this . _isStopped ) return this ;
155+
156+ span . setAttribute ( "replication_client.name" , this . options . name ) ;
157+ span . setAttribute ( "replication_client.table" , this . options . table ) ;
158+ span . setAttribute ( "replication_client.slot_name" , this . options . slotName ) ;
159+ span . setAttribute ( "replication_client.publication_name" , this . options . publicationName ) ;
160+
161+ this . _isStopped = true ;
162+ // Clean up leader lock heartbeat
163+ if ( this . leaderLockHeartbeatTimer ) {
164+ clearInterval ( this . leaderLockHeartbeatTimer ) ;
165+ this . leaderLockHeartbeatTimer = null ;
166+ }
167+ // Clean up ack interval
168+ if ( this . ackIntervalTimer ) {
169+ clearInterval ( this . ackIntervalTimer ) ;
170+ this . ackIntervalTimer = null ;
171+ }
172+ // Release leader lock if held
173+ await this . #releaseLeaderLock( ) ;
162174
163- this . connection ?. removeAllListeners ( ) ;
164- this . connection = null ;
175+ this . connection ?. removeAllListeners ( ) ;
176+ this . connection = null ;
165177
166- if ( this . client ) {
167- this . client . removeAllListeners ( ) ;
178+ if ( this . client ) {
179+ this . client . removeAllListeners ( ) ;
168180
169- const [ endError ] = await tryCatch ( this . client . end ( ) ) ;
181+ const [ endError ] = await tryCatch ( this . client . end ( ) ) ;
170182
171- if ( endError ) {
172- this . logger . error ( "Failed to end client" , {
173- name : this . options . name ,
174- error : endError ,
175- } ) ;
176- } else {
177- this . logger . info ( "Ended client" , {
178- name : this . options . name ,
179- } ) ;
183+ if ( endError ) {
184+ this . logger . error ( "Failed to end client" , {
185+ name : this . options . name ,
186+ error : endError ,
187+ } ) ;
188+ } else {
189+ this . logger . info ( "Ended client" , {
190+ name : this . options . name ,
191+ } ) ;
192+ }
193+ this . client = null ;
180194 }
181- this . client = null ;
182- }
183195
184- // clear any intervals
185- if ( this . leaderLockHeartbeatTimer ) {
186- clearInterval ( this . leaderLockHeartbeatTimer ) ;
187- this . leaderLockHeartbeatTimer = null ;
188- }
196+ // clear any intervals
197+ if ( this . leaderLockHeartbeatTimer ) {
198+ clearInterval ( this . leaderLockHeartbeatTimer ) ;
199+ this . leaderLockHeartbeatTimer = null ;
200+ }
189201
190- if ( this . ackIntervalTimer ) {
191- clearInterval ( this . ackIntervalTimer ) ;
192- this . ackIntervalTimer = null ;
193- }
202+ if ( this . ackIntervalTimer ) {
203+ clearInterval ( this . ackIntervalTimer ) ;
204+ this . ackIntervalTimer = null ;
205+ }
194206
195- return this ;
207+ return this ;
208+ } ) ;
196209 }
197210
198211 public async teardown ( ) : Promise < boolean > {
@@ -523,34 +536,43 @@ export class LogicalReplicationClient {
523536 public async acknowledge ( lsn : string ) : Promise < boolean > {
524537 if ( this . _isStopped ) return false ;
525538 if ( ! this . connection ) return false ;
526- // WAL LSN split
527- const slice = lsn . split ( "/" ) ;
528- let [ upperWAL , lowerWAL ] : [ number , number ] = [ parseInt ( slice [ 0 ] , 16 ) , parseInt ( slice [ 1 ] , 16 ) ] ;
529- // Timestamp as microseconds since midnight 2000-01-01
530- const now = Date . now ( ) - 946080000000 ;
531- const upperTimestamp = Math . floor ( now / 4294967.296 ) ;
532- const lowerTimestamp = Math . floor ( now - upperTimestamp * 4294967.296 ) ;
533- if ( lowerWAL === 4294967295 ) {
534- upperWAL = upperWAL + 1 ;
535- lowerWAL = 0 ;
536- } else {
537- lowerWAL = lowerWAL + 1 ;
538- }
539- const response = Buffer . alloc ( 34 ) ;
540- response . fill ( 0x72 ) ; // 'r'
541- response . writeUInt32BE ( upperWAL , 1 ) ;
542- response . writeUInt32BE ( lowerWAL , 5 ) ;
543- response . writeUInt32BE ( upperWAL , 9 ) ;
544- response . writeUInt32BE ( lowerWAL , 13 ) ;
545- response . writeUInt32BE ( upperWAL , 17 ) ;
546- response . writeUInt32BE ( lowerWAL , 21 ) ;
547- response . writeUInt32BE ( upperTimestamp , 25 ) ;
548- response . writeUInt32BE ( lowerTimestamp , 29 ) ;
549- response . writeInt8 ( 0 , 33 ) ;
550- // @ts -ignore
551- this . connection . sendCopyFromChunk ( response ) ;
552- this . lastAckTimestamp = Date . now ( ) ;
553- return true ;
539+
540+ return await startSpan ( this . _tracer , "logical_replication_client.acknowledge" , async ( span ) => {
541+ span . setAttribute ( "replication_client.lsn" , lsn ) ;
542+ span . setAttribute ( "replication_client.name" , this . options . name ) ;
543+ span . setAttribute ( "replication_client.table" , this . options . table ) ;
544+ span . setAttribute ( "replication_client.slot_name" , this . options . slotName ) ;
545+ span . setAttribute ( "replication_client.publication_name" , this . options . publicationName ) ;
546+
547+ // WAL LSN split
548+ const slice = lsn . split ( "/" ) ;
549+ let [ upperWAL , lowerWAL ] : [ number , number ] = [ parseInt ( slice [ 0 ] , 16 ) , parseInt ( slice [ 1 ] , 16 ) ] ;
550+ // Timestamp as microseconds since midnight 2000-01-01
551+ const now = Date . now ( ) - 946080000000 ;
552+ const upperTimestamp = Math . floor ( now / 4294967.296 ) ;
553+ const lowerTimestamp = Math . floor ( now - upperTimestamp * 4294967.296 ) ;
554+ if ( lowerWAL === 4294967295 ) {
555+ upperWAL = upperWAL + 1 ;
556+ lowerWAL = 0 ;
557+ } else {
558+ lowerWAL = lowerWAL + 1 ;
559+ }
560+ const response = Buffer . alloc ( 34 ) ;
561+ response . fill ( 0x72 ) ; // 'r'
562+ response . writeUInt32BE ( upperWAL , 1 ) ;
563+ response . writeUInt32BE ( lowerWAL , 5 ) ;
564+ response . writeUInt32BE ( upperWAL , 9 ) ;
565+ response . writeUInt32BE ( lowerWAL , 13 ) ;
566+ response . writeUInt32BE ( upperWAL , 17 ) ;
567+ response . writeUInt32BE ( lowerWAL , 21 ) ;
568+ response . writeUInt32BE ( upperTimestamp , 25 ) ;
569+ response . writeUInt32BE ( lowerTimestamp , 29 ) ;
570+ response . writeInt8 ( 0 , 33 ) ;
571+ // @ts -ignore
572+ this . connection . sendCopyFromChunk ( response ) ;
573+ this . lastAckTimestamp = Date . now ( ) ;
574+ return true ;
575+ } ) ;
554576 }
555577
556578 async #acquireLeaderLock( ) : Promise < boolean > {
0 commit comments