Skip to content

Commit cfbcd27

Browse files
committed
fix(telemetry): address review feedback on aggregator and close()
- Snapshot driverConfig on each statement at first event so a later CONNECTION_OPEN can't retroactively rewrite the config reported by in-flight statements (and their buffered errors). - Attach a defensive .catch() to the fire-and-forget exporter.export() call so any future regression that leaks a rejection logs at debug rather than surfacing as an unhandled promise rejection. - Document the unref()'d flush timer on DBSQLClient.close(): callers must await close() on shutdown to drain buffered telemetry; otherwise metrics between flush ticks are lost. Co-authored-by: Isaac
1 parent c108048 commit cfbcd27

2 files changed

Lines changed: 27 additions & 6 deletions

File tree

lib/DBSQLClient.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,15 @@ export default class DBSQLClient extends EventEmitter implements IDBSQLClient, I
601601
return session;
602602
}
603603

604+
/**
605+
* Closes the client, releasing sessions and telemetry resources.
606+
*
607+
* The internal telemetry flush timer uses `setInterval(...).unref()` so it
608+
* cannot keep the Node.js process alive on its own. As a consequence, any
609+
* telemetry buffered between flush ticks is lost if the process exits
610+
* without calling `close()`. Long-lived applications should `await` this
611+
* method on shutdown so the aggregator drains its remaining metrics.
612+
*/
604613
public async close(): Promise<void> {
605614
await this.sessions.closeAll();
606615

lib/telemetry/MetricsAggregator.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ interface StatementTelemetryDetails {
4444
bytesDownloaded: number;
4545
pollCount: number;
4646
compressionEnabled?: boolean;
47+
// Snapshot of driverConfig taken when the statement started, so a later
48+
// CONNECTION_OPEN on the same client can't retroactively rewrite this
49+
// statement's reported config.
50+
driverConfig?: DriverConfiguration;
4751
errors: TelemetryEvent[];
4852
}
4953

@@ -282,6 +286,7 @@ export default class MetricsAggregator {
282286
chunkSumLatencyMs: 0,
283287
bytesDownloaded: 0,
284288
pollCount: 0,
289+
driverConfig: this.driverConfig,
285290
errors: [],
286291
});
287292
}
@@ -303,14 +308,16 @@ export default class MetricsAggregator {
303308
return;
304309
}
305310

306-
// Create statement metric (include cached driver config for context)
311+
// Create statement metric (use config snapshotted at statement start;
312+
// fall back to current cached config if the statement predates any
313+
// CONNECTION_OPEN we observed)
307314
const metric: TelemetryMetric = {
308315
metricType: 'statement',
309316
timestamp: details.startTime,
310317
sessionId: details.sessionId,
311318
statementId: details.statementId,
312319
workspaceId: details.workspaceId,
313-
driverConfig: this.driverConfig,
320+
driverConfig: details.driverConfig ?? this.driverConfig,
314321
operationType: details.operationType,
315322
latencyMs: details.executionLatencyMs,
316323
resultFormat: details.resultFormat,
@@ -325,15 +332,16 @@ export default class MetricsAggregator {
325332

326333
this.addPendingMetric(metric);
327334

328-
// Add buffered error metrics (include cached driver config for context)
335+
// Add buffered error metrics (use the same per-statement driverConfig
336+
// snapshot, so errors and the statement metric agree on config)
329337
for (const errorEvent of details.errors) {
330338
const errorMetric: TelemetryMetric = {
331339
metricType: 'error',
332340
timestamp: errorEvent.timestamp,
333341
sessionId: details.sessionId,
334342
statementId: details.statementId,
335343
workspaceId: details.workspaceId,
336-
driverConfig: this.driverConfig,
344+
driverConfig: details.driverConfig ?? this.driverConfig,
337345
errorName: errorEvent.errorName,
338346
errorMessage: errorEvent.errorMessage,
339347
};
@@ -376,8 +384,12 @@ export default class MetricsAggregator {
376384
const metricsToExport = [...this.pendingMetrics];
377385
this.pendingMetrics = [];
378386

379-
// Export metrics (exporter.export never throws)
380-
this.exporter.export(metricsToExport);
387+
// Export metrics. exporter.export() is documented to never throw, but
388+
// attach a .catch defensively so a future regression that leaks a
389+
// rejection doesn't surface as an unhandled promise rejection.
390+
this.exporter.export(metricsToExport).catch((error: any) => {
391+
logger.log(LogLevel.debug, `Telemetry export rejected: ${error?.message ?? error}`);
392+
});
381393

382394
// Reset timer to avoid rapid successive flushes (e.g., batch flush at 25s then timer flush at 30s)
383395
// This ensures consistent spacing between exports and helps avoid rate limiting

0 commit comments

Comments
 (0)