taist 0.2.16 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/instrument.js +39 -0
- package/lib/trace-reporter.js +84 -10
- package/package.json +1 -1
- package/types/instrument.d.ts +22 -0
package/instrument.js
CHANGED
|
@@ -92,6 +92,45 @@ export async function flushTraces() {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Gracefully shutdown taist - call this before process exit.
|
|
97
|
+
* Waits for pending socket writes to complete and flushes remaining traces.
|
|
98
|
+
*
|
|
99
|
+
* This is the recommended way to ensure all traces are captured when integrating
|
|
100
|
+
* with applications that have their own shutdown hooks (e.g., Directus onShutdown).
|
|
101
|
+
*
|
|
102
|
+
* @param {number} timeoutMs - Maximum time to wait for pending writes (default: 2000ms)
|
|
103
|
+
* @returns {Promise<void>}
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* // Directus integration
|
|
107
|
+
* import { gracefulShutdown } from 'taist/instrument';
|
|
108
|
+
*
|
|
109
|
+
* export default defineHook(({ onShutdown }) => {
|
|
110
|
+
* onShutdown(async () => {
|
|
111
|
+
* await gracefulShutdown();
|
|
112
|
+
* });
|
|
113
|
+
* });
|
|
114
|
+
*/
|
|
115
|
+
export async function gracefulShutdown(timeoutMs = 2000) {
|
|
116
|
+
if (!reporter) return;
|
|
117
|
+
|
|
118
|
+
// Prevent SIGTERM handler from doing redundant work
|
|
119
|
+
if (reporter.shuttingDown || reporter.closed) return;
|
|
120
|
+
reporter.shuttingDown = true;
|
|
121
|
+
|
|
122
|
+
// 1. Wait for pending writes to complete
|
|
123
|
+
if (reporter._waitForPendingWrites) {
|
|
124
|
+
await reporter._waitForPendingWrites(timeoutMs);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 2. Final flush of any buffered traces
|
|
128
|
+
await reporter.flush();
|
|
129
|
+
|
|
130
|
+
// 3. Close socket cleanly
|
|
131
|
+
reporter.close();
|
|
132
|
+
}
|
|
133
|
+
|
|
95
134
|
// Log initialization
|
|
96
135
|
if (tracer.options.enabled) {
|
|
97
136
|
logger.log('Instrumentation enabled');
|
package/lib/trace-reporter.js
CHANGED
|
@@ -37,6 +37,7 @@ export class TraceReporter extends EventEmitter {
|
|
|
37
37
|
this.shuttingDown = false; // Prevents race between shutdown signal and SIGTERM
|
|
38
38
|
this.pendingWrites = 0; // Track writes that have been initiated but callback hasn't fired
|
|
39
39
|
this.pendingWriteResolvers = []; // Resolvers to call when pendingWrites reaches 0
|
|
40
|
+
this._flushScheduled = false; // For micro-batching in flushImmediate mode
|
|
40
41
|
|
|
41
42
|
logger.debug("[reporter] Created with socketPath:", this.socketPath, "flushImmediate:", this.flushImmediate);
|
|
42
43
|
|
|
@@ -47,9 +48,9 @@ export class TraceReporter extends EventEmitter {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
_setupExitHandlers() {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
// Synchronous cleanup for exit events
|
|
52
|
+
const syncCleanup = (signal) => {
|
|
53
|
+
logger.debug("[reporter] Sync cleanup triggered by:", signal, "buffer size:", this.buffer.length);
|
|
53
54
|
if (this.shuttingDown) {
|
|
54
55
|
logger.debug("[reporter] Shutdown already in progress, skipping cleanup");
|
|
55
56
|
return;
|
|
@@ -60,10 +61,75 @@ export class TraceReporter extends EventEmitter {
|
|
|
60
61
|
}
|
|
61
62
|
};
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
// Async cleanup for signals - waits for pending writes then exits
|
|
65
|
+
const signalCleanup = async (signal) => {
|
|
66
|
+
const lifecycleDebug = process.env.TAIST_TRACE_LIFECYCLE === 'true';
|
|
67
|
+
if (lifecycleDebug) {
|
|
68
|
+
console.log(`[LIFECYCLE reporter] Signal ${signal} received, pendingWrites:`, this.pendingWrites);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (this.shuttingDown) {
|
|
72
|
+
logger.debug("[reporter] Shutdown already in progress, skipping cleanup");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.shuttingDown = true;
|
|
76
|
+
|
|
77
|
+
// Wait for pending writes to complete (up to 2 seconds)
|
|
78
|
+
await this._waitForPendingWrites(2000);
|
|
79
|
+
|
|
80
|
+
if (lifecycleDebug) {
|
|
81
|
+
console.log(`[LIFECYCLE reporter] After waiting, pendingWrites:`, this.pendingWrites, 'buffer:', this.buffer.length);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Final flush and close
|
|
85
|
+
if (!this.closed) {
|
|
86
|
+
this.flushSync();
|
|
87
|
+
this.close();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Exit the process after cleanup
|
|
91
|
+
process.exit(0);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
process.on("beforeExit", () => syncCleanup("beforeExit"));
|
|
95
|
+
process.on("exit", () => syncCleanup("exit"));
|
|
96
|
+
process.on("SIGINT", () => signalCleanup("SIGINT"));
|
|
97
|
+
process.on("SIGTERM", () => signalCleanup("SIGTERM"));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Wait for pending socket writes to complete.
|
|
102
|
+
* @param {number} timeout - Maximum time to wait in milliseconds
|
|
103
|
+
*/
|
|
104
|
+
async _waitForPendingWrites(timeout = 2000) {
|
|
105
|
+
if (this.pendingWrites === 0) return;
|
|
106
|
+
|
|
107
|
+
const lifecycleDebug = process.env.TAIST_TRACE_LIFECYCLE === 'true';
|
|
108
|
+
const startTime = Date.now();
|
|
109
|
+
|
|
110
|
+
return new Promise((resolve) => {
|
|
111
|
+
const checkPending = () => {
|
|
112
|
+
if (this.pendingWrites === 0) {
|
|
113
|
+
if (lifecycleDebug) {
|
|
114
|
+
console.log('[LIFECYCLE reporter] All pending writes completed');
|
|
115
|
+
}
|
|
116
|
+
resolve();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if ((Date.now() - startTime) >= timeout) {
|
|
121
|
+
if (lifecycleDebug) {
|
|
122
|
+
console.log('[LIFECYCLE reporter] Timeout waiting for pending writes, still have:', this.pendingWrites);
|
|
123
|
+
}
|
|
124
|
+
resolve();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Poll at 10ms intervals
|
|
129
|
+
setTimeout(checkPending, 10);
|
|
130
|
+
};
|
|
131
|
+
checkPending();
|
|
132
|
+
});
|
|
67
133
|
}
|
|
68
134
|
|
|
69
135
|
async connect() {
|
|
@@ -313,10 +379,18 @@ export class TraceReporter extends EventEmitter {
|
|
|
313
379
|
});
|
|
314
380
|
}
|
|
315
381
|
|
|
316
|
-
// Flush
|
|
317
|
-
//
|
|
382
|
+
// Flush strategy:
|
|
383
|
+
// - flushImmediate=true: Use micro-batching (setImmediate) to batch traces from the same tick
|
|
384
|
+
// This significantly reduces socket writes (e.g., 2762 individual writes → ~100 batched writes)
|
|
385
|
+
// - flushImmediate=false: Traditional batching by size (flushes when buffer reaches batchSize)
|
|
318
386
|
if (this.flushImmediate) {
|
|
319
|
-
this.
|
|
387
|
+
if (!this._flushScheduled) {
|
|
388
|
+
this._flushScheduled = true;
|
|
389
|
+
setImmediate(() => {
|
|
390
|
+
this._flushScheduled = false;
|
|
391
|
+
this.flush().catch(() => {});
|
|
392
|
+
});
|
|
393
|
+
}
|
|
320
394
|
} else if (this.buffer.length >= this.batchSize) {
|
|
321
395
|
this.flush().catch(() => {});
|
|
322
396
|
}
|
package/package.json
CHANGED
package/types/instrument.d.ts
CHANGED
|
@@ -111,6 +111,28 @@ export declare const reporter: TraceReporter;
|
|
|
111
111
|
*/
|
|
112
112
|
export declare function flushTraces(): Promise<void>;
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Gracefully shutdown taist - call this before process exit.
|
|
116
|
+
* Waits for pending socket writes to complete and flushes remaining traces.
|
|
117
|
+
*
|
|
118
|
+
* This is the recommended way to ensure all traces are captured when integrating
|
|
119
|
+
* with applications that have their own shutdown hooks (e.g., Directus onShutdown).
|
|
120
|
+
*
|
|
121
|
+
* @param timeoutMs Maximum time to wait for pending writes (default: 2000ms)
|
|
122
|
+
* @returns Promise that resolves when shutdown is complete
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* // Directus integration
|
|
126
|
+
* import { gracefulShutdown } from 'taist/instrument';
|
|
127
|
+
*
|
|
128
|
+
* export default defineHook(({ onShutdown }) => {
|
|
129
|
+
* onShutdown(async () => {
|
|
130
|
+
* await gracefulShutdown();
|
|
131
|
+
* });
|
|
132
|
+
* });
|
|
133
|
+
*/
|
|
134
|
+
export declare function gracefulShutdown(timeoutMs?: number): Promise<void>;
|
|
135
|
+
|
|
114
136
|
/**
|
|
115
137
|
* Auto-instrument a module's exports
|
|
116
138
|
* @param moduleExports Module exports object
|