@@ -7,6 +7,7 @@ import { createRedisClient } from "@internal/redis";
7
7
import { Logger } from "@trigger.dev/core/logger" ;
8
8
import { LogicalReplicationClientError } from "./errors.js" ;
9
9
import { PgoutputMessage , PgoutputParser , getPgoutputStartReplicationSQL } from "./pgoutput.js" ;
10
+ import { startSpan , trace , Tracer } from "@internal/tracing" ;
10
11
11
12
export interface LogicalReplicationClientOptions {
12
13
/**
@@ -70,6 +71,8 @@ export interface LogicalReplicationClientOptions {
70
71
* The actions to publish to the publication.
71
72
*/
72
73
publicationActions ?: Array < "insert" | "update" | "delete" | "truncate" > ;
74
+
75
+ tracer ?: Tracer ;
73
76
}
74
77
75
78
export type LogicalReplicationClientEvents = {
@@ -101,6 +104,7 @@ export class LogicalReplicationClient {
101
104
private lastAckTimestamp : number = 0 ;
102
105
private ackIntervalTimer : NodeJS . Timeout | null = null ;
103
106
private _isStopped : boolean = false ;
107
+ private _tracer : Tracer ;
104
108
105
109
public get lastLsn ( ) : string {
106
110
return this . lastAcknowledgedLsn ?? "0/00000000" ;
@@ -113,6 +117,7 @@ export class LogicalReplicationClient {
113
117
constructor ( options : LogicalReplicationClientOptions ) {
114
118
this . options = options ;
115
119
this . logger = options . logger ?? new Logger ( "LogicalReplicationClient" , "info" ) ;
120
+ this . _tracer = options . tracer ?? trace . getTracer ( "logical-replication-client" ) ;
116
121
117
122
this . autoAcknowledge =
118
123
typeof options . autoAcknowledge === "boolean" ? options . autoAcknowledge : true ;
@@ -145,54 +150,62 @@ export class LogicalReplicationClient {
145
150
}
146
151
147
152
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( ) ;
162
174
163
- this . connection ?. removeAllListeners ( ) ;
164
- this . connection = null ;
175
+ this . connection ?. removeAllListeners ( ) ;
176
+ this . connection = null ;
165
177
166
- if ( this . client ) {
167
- this . client . removeAllListeners ( ) ;
178
+ if ( this . client ) {
179
+ this . client . removeAllListeners ( ) ;
168
180
169
- const [ endError ] = await tryCatch ( this . client . end ( ) ) ;
181
+ const [ endError ] = await tryCatch ( this . client . end ( ) ) ;
170
182
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 ;
180
194
}
181
- this . client = null ;
182
- }
183
195
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
+ }
189
201
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
+ }
194
206
195
- return this ;
207
+ return this ;
208
+ } ) ;
196
209
}
197
210
198
211
public async teardown ( ) : Promise < boolean > {
@@ -523,34 +536,43 @@ export class LogicalReplicationClient {
523
536
public async acknowledge ( lsn : string ) : Promise < boolean > {
524
537
if ( this . _isStopped ) return false ;
525
538
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
+ } ) ;
554
576
}
555
577
556
578
async #acquireLeaderLock( ) : Promise < boolean > {
0 commit comments