@sentienguard/apm 1.0.21 → 1.0.22-debug.1
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/README.md +141 -141
- package/package.json +1 -1
- package/src/config.js +179 -179
- package/src/dependencies.js +374 -374
- package/src/errors.js +132 -132
- package/src/index.d.ts +120 -120
- package/src/index.js +251 -242
- package/src/mongodb.js +73 -10
- package/src/openai.js +520 -520
- package/src/spanExporter.js +6 -0
- package/src/traceSpanExporter.js +165 -186
- package/src/tracing.js +5 -0
- package/src/transport.js +8 -0
package/src/index.js
CHANGED
|
@@ -1,242 +1,251 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SentienGuard APM SDK
|
|
3
|
-
*
|
|
4
|
-
* Minimal, production-safe APM that runs inside client applications
|
|
5
|
-
* and sends aggregated metrics to the SentienGuard backend.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* import "@sentienguard/apm";
|
|
9
|
-
*
|
|
10
|
-
* That's it. No function calls, no setup code, no decorators.
|
|
11
|
-
*
|
|
12
|
-
* Configuration via environment variables:
|
|
13
|
-
* SENTIENGUARD_APM_KEY=xxxx (required)
|
|
14
|
-
* SENTIENGUARD_SERVICE=my-api (required)
|
|
15
|
-
* SENTIENGUARD_ENV=production (optional, default: production)
|
|
16
|
-
* SENTIENGUARD_ENDPOINT=https://... (optional)
|
|
17
|
-
* SENTIENGUARD_TRACES_ENDPOINT=https://... (optional, raw span ingest; default derived from SENTIENGUARD_ENDPOINT)
|
|
18
|
-
* SENTIENGUARD_FLUSH_INTERVAL=10 (optional, seconds)
|
|
19
|
-
* SENTIENGUARD_TRACING=false (optional, disable OpenTelemetry / W3C propagation; use legacy HTTP patches)
|
|
20
|
-
* SENTIENGUARD_TRACE_SAMPLE_RATE=0.05 (optional, export sampling for raw traces only; metrics are not sampled)
|
|
21
|
-
* SENTIENGUARD_TRACE_MAX_QUEUE_SIZE=2048 (optional, drop-on-pressure queue size for raw spans)
|
|
22
|
-
* SENTIENGUARD_TRACE_MAX_BATCH_SIZE=256 (optional, batch size for raw span export)
|
|
23
|
-
* SENTIENGUARD_TRACE_LOCAL_HTTP=true (optional, record outgoing HTTP to localhost as dependencies; use with SENTIENGUARD_PEER_SERVICE_MAP)
|
|
24
|
-
* SENTIENGUARD_PEER_SERVICE_MAP=3001:service-b,3002:other (optional, port -> callee name for local peers)
|
|
25
|
-
*
|
|
26
|
-
* No config → SDK disables itself silently.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import config, { isEnabled, debug, getConfig, loadConfig } from './config.js';
|
|
30
|
-
import { instrumentHttp, expressMiddleware, fastifyPlugin } from './instrumentation.js';
|
|
31
|
-
import { instrumentDependencies, instrumentFetch } from './dependencies.js';
|
|
32
|
-
import { startErrorCapture, expressErrorMiddleware, fastifyErrorHandler } from './errors.js';
|
|
33
|
-
import { startFlushing, stopFlushing, finalFlush, flush } from './transport.js';
|
|
34
|
-
import { getAggregator } from './aggregator.js';
|
|
35
|
-
import { normalizeRoute, extractRoute, RouteRegistry } from './normalizer.js';
|
|
36
|
-
import { instrumentMongoDB, autoInstrumentMongoDB, stopMongoDBInstrumentation } from './mongodb.js';
|
|
37
|
-
import { instrumentOpenAI, stopOpenAIInstrumentation } from './openai.js';
|
|
38
|
-
import { createBreaker, wrapMongoOperation, getBreakerStats, shutdownBreakers } from './circuitBreaker.js';
|
|
39
|
-
import { startTracing, shutdownTracing, getActiveTraceId, isTracingActive } from './tracing.js';
|
|
40
|
-
import { flushTraceQueue } from './traceTransport.js';
|
|
41
|
-
|
|
42
|
-
let isInitialized = false;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Initialize the SDK
|
|
46
|
-
* Called automatically on import
|
|
47
|
-
*/
|
|
48
|
-
function initialize() {
|
|
49
|
-
if (isInitialized) {
|
|
50
|
-
debug('SDK already initialized');
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Load config from env vars (lazy — allows dotenv to load first)
|
|
55
|
-
loadConfig();
|
|
56
|
-
|
|
57
|
-
// Check if SDK should be enabled
|
|
58
|
-
if (!isEnabled()) {
|
|
59
|
-
// Silently disable - this is expected behavior
|
|
60
|
-
debug('SDK disabled (missing API key or service name)');
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Warn if this import is not first (other modules may have created servers already)
|
|
65
|
-
// We can't reliably detect this, so just log for debugging
|
|
66
|
-
debug(`Initializing SDK for service: ${config.service}`);
|
|
67
|
-
debug(`Environment: ${config.environment}`);
|
|
68
|
-
debug(`Endpoint: ${config.endpoint}`);
|
|
69
|
-
debug(`Flush interval: ${config.flushInterval}s`);
|
|
70
|
-
|
|
71
|
-
// OpenTelemetry: W3C propagation + span → metrics (same ingest as before); legacy patches if this fails or is off
|
|
72
|
-
const tracingOn = startTracing();
|
|
73
|
-
// Ensure fetch() dependency edges are captured (works in both tracing and legacy modes)
|
|
74
|
-
instrumentFetch();
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
1
|
+
/**
|
|
2
|
+
* SentienGuard APM SDK
|
|
3
|
+
*
|
|
4
|
+
* Minimal, production-safe APM that runs inside client applications
|
|
5
|
+
* and sends aggregated metrics to the SentienGuard backend.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import "@sentienguard/apm";
|
|
9
|
+
*
|
|
10
|
+
* That's it. No function calls, no setup code, no decorators.
|
|
11
|
+
*
|
|
12
|
+
* Configuration via environment variables:
|
|
13
|
+
* SENTIENGUARD_APM_KEY=xxxx (required)
|
|
14
|
+
* SENTIENGUARD_SERVICE=my-api (required)
|
|
15
|
+
* SENTIENGUARD_ENV=production (optional, default: production)
|
|
16
|
+
* SENTIENGUARD_ENDPOINT=https://... (optional)
|
|
17
|
+
* SENTIENGUARD_TRACES_ENDPOINT=https://... (optional, raw span ingest; default derived from SENTIENGUARD_ENDPOINT)
|
|
18
|
+
* SENTIENGUARD_FLUSH_INTERVAL=10 (optional, seconds)
|
|
19
|
+
* SENTIENGUARD_TRACING=false (optional, disable OpenTelemetry / W3C propagation; use legacy HTTP patches)
|
|
20
|
+
* SENTIENGUARD_TRACE_SAMPLE_RATE=0.05 (optional, export sampling for raw traces only; metrics are not sampled)
|
|
21
|
+
* SENTIENGUARD_TRACE_MAX_QUEUE_SIZE=2048 (optional, drop-on-pressure queue size for raw spans)
|
|
22
|
+
* SENTIENGUARD_TRACE_MAX_BATCH_SIZE=256 (optional, batch size for raw span export)
|
|
23
|
+
* SENTIENGUARD_TRACE_LOCAL_HTTP=true (optional, record outgoing HTTP to localhost as dependencies; use with SENTIENGUARD_PEER_SERVICE_MAP)
|
|
24
|
+
* SENTIENGUARD_PEER_SERVICE_MAP=3001:service-b,3002:other (optional, port -> callee name for local peers)
|
|
25
|
+
*
|
|
26
|
+
* No config → SDK disables itself silently.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import config, { isEnabled, debug, getConfig, loadConfig } from './config.js';
|
|
30
|
+
import { instrumentHttp, expressMiddleware, fastifyPlugin } from './instrumentation.js';
|
|
31
|
+
import { instrumentDependencies, instrumentFetch } from './dependencies.js';
|
|
32
|
+
import { startErrorCapture, expressErrorMiddleware, fastifyErrorHandler } from './errors.js';
|
|
33
|
+
import { startFlushing, stopFlushing, finalFlush, flush } from './transport.js';
|
|
34
|
+
import { getAggregator } from './aggregator.js';
|
|
35
|
+
import { normalizeRoute, extractRoute, RouteRegistry } from './normalizer.js';
|
|
36
|
+
import { instrumentMongoDB, autoInstrumentMongoDB, stopMongoDBInstrumentation } from './mongodb.js';
|
|
37
|
+
import { instrumentOpenAI, stopOpenAIInstrumentation } from './openai.js';
|
|
38
|
+
import { createBreaker, wrapMongoOperation, getBreakerStats, shutdownBreakers } from './circuitBreaker.js';
|
|
39
|
+
import { startTracing, shutdownTracing, getActiveTraceId, isTracingActive } from './tracing.js';
|
|
40
|
+
import { flushTraceQueue } from './traceTransport.js';
|
|
41
|
+
|
|
42
|
+
let isInitialized = false;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Initialize the SDK
|
|
46
|
+
* Called automatically on import
|
|
47
|
+
*/
|
|
48
|
+
function initialize() {
|
|
49
|
+
if (isInitialized) {
|
|
50
|
+
debug('SDK already initialized');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Load config from env vars (lazy — allows dotenv to load first)
|
|
55
|
+
loadConfig();
|
|
56
|
+
|
|
57
|
+
// Check if SDK should be enabled
|
|
58
|
+
if (!isEnabled()) {
|
|
59
|
+
// Silently disable - this is expected behavior
|
|
60
|
+
debug('SDK disabled (missing API key or service name)');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Warn if this import is not first (other modules may have created servers already)
|
|
65
|
+
// We can't reliably detect this, so just log for debugging
|
|
66
|
+
debug(`Initializing SDK for service: ${config.service}`);
|
|
67
|
+
debug(`Environment: ${config.environment}`);
|
|
68
|
+
debug(`Endpoint: ${config.endpoint}`);
|
|
69
|
+
debug(`Flush interval: ${config.flushInterval}s`);
|
|
70
|
+
|
|
71
|
+
// OpenTelemetry: W3C propagation + span → metrics (same ingest as before); legacy patches if this fails or is off
|
|
72
|
+
const tracingOn = startTracing();
|
|
73
|
+
// Ensure fetch() dependency edges are captured (works in both tracing and legacy modes)
|
|
74
|
+
instrumentFetch();
|
|
75
|
+
// Bun: OTel instrumentation-http/-express don't actually patch Bun's HTTP layer even when
|
|
76
|
+
// NodeSDK.start() succeeds. Force-install legacy patches under Bun so we actually see traffic.
|
|
77
|
+
const isBunRuntime = typeof globalThis.Bun !== 'undefined';
|
|
78
|
+
if (!tracingOn || isBunRuntime) {
|
|
79
|
+
instrumentHttp();
|
|
80
|
+
instrumentDependencies();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// #region agent log
|
|
84
|
+
try {
|
|
85
|
+
console.log('[SG-APM-DBG]', JSON.stringify({sessionId:'ecc573',runId:'post-fix',hypothesisId:'A,B',location:'index.js:initialize',message:'SDK init runtime + instrumentation flow (Bun-aware)',data:{isBun:isBunRuntime,bunVersion:globalThis.Bun?.version||null,nodeVersion:process.versions?.node||null,tracingOn,legacyHttpInstalled:!tracingOn||isBunRuntime,service:config.service,endpoint:config.endpoint,argv0:process.argv0,execPath:process.execPath},timestamp:Date.now()}));
|
|
86
|
+
} catch {}
|
|
87
|
+
// #endregion
|
|
88
|
+
|
|
89
|
+
// Auto-instrument MongoDB if available
|
|
90
|
+
autoInstrumentMongoDB();
|
|
91
|
+
|
|
92
|
+
// Start error capture
|
|
93
|
+
startErrorCapture();
|
|
94
|
+
|
|
95
|
+
// Start periodic flush
|
|
96
|
+
startFlushing();
|
|
97
|
+
|
|
98
|
+
// Handle graceful shutdown
|
|
99
|
+
setupGracefulShutdown();
|
|
100
|
+
|
|
101
|
+
isInitialized = true;
|
|
102
|
+
debug('SDK initialized successfully');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Setup graceful shutdown handlers
|
|
107
|
+
*/
|
|
108
|
+
function setupGracefulShutdown() {
|
|
109
|
+
const shutdown = async (signal) => {
|
|
110
|
+
debug(`Received ${signal}, performing graceful shutdown`);
|
|
111
|
+
|
|
112
|
+
// Stop accepting new data
|
|
113
|
+
stopFlushing();
|
|
114
|
+
|
|
115
|
+
// Final flush
|
|
116
|
+
await finalFlush();
|
|
117
|
+
// Best-effort flush of queued raw spans
|
|
118
|
+
await flushTraceQueue();
|
|
119
|
+
|
|
120
|
+
debug('Shutdown complete');
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Handle common shutdown signals
|
|
124
|
+
process.once('SIGTERM', () => shutdown('SIGTERM'));
|
|
125
|
+
process.once('SIGINT', () => shutdown('SIGINT'));
|
|
126
|
+
|
|
127
|
+
// Handle process exit
|
|
128
|
+
process.once('beforeExit', () => shutdown('beforeExit'));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Shutdown the SDK
|
|
133
|
+
* Call this before process exit for clean shutdown
|
|
134
|
+
*/
|
|
135
|
+
async function shutdown() {
|
|
136
|
+
if (!isInitialized) return;
|
|
137
|
+
|
|
138
|
+
debug('Shutting down SDK');
|
|
139
|
+
|
|
140
|
+
await shutdownTracing();
|
|
141
|
+
// After OTel shutdown, best-effort drain any serialized spans still queued in transport.
|
|
142
|
+
await flushTraceQueue();
|
|
143
|
+
|
|
144
|
+
// Stop MongoDB instrumentation
|
|
145
|
+
stopMongoDBInstrumentation();
|
|
146
|
+
|
|
147
|
+
// Stop OpenAI instrumentation
|
|
148
|
+
stopOpenAIInstrumentation();
|
|
149
|
+
|
|
150
|
+
// Shutdown circuit breakers
|
|
151
|
+
shutdownBreakers();
|
|
152
|
+
|
|
153
|
+
stopFlushing();
|
|
154
|
+
await finalFlush();
|
|
155
|
+
|
|
156
|
+
isInitialized = false;
|
|
157
|
+
debug('SDK shutdown complete');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get current SDK status
|
|
162
|
+
*/
|
|
163
|
+
function getStatus() {
|
|
164
|
+
const aggregator = getAggregator();
|
|
165
|
+
const stats = aggregator.getStats();
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
enabled: isEnabled(),
|
|
169
|
+
initialized: isInitialized,
|
|
170
|
+
tracing: isTracingActive(),
|
|
171
|
+
config: {
|
|
172
|
+
service: config.service,
|
|
173
|
+
environment: config.environment,
|
|
174
|
+
flushInterval: config.flushInterval
|
|
175
|
+
},
|
|
176
|
+
stats
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Initialize when import runs (retry on nextTick if dotenv loads env vars later)
|
|
181
|
+
initialize();
|
|
182
|
+
|
|
183
|
+
if (!isInitialized) {
|
|
184
|
+
process.nextTick(() => {
|
|
185
|
+
loadConfig({ force: true });
|
|
186
|
+
initialize();
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Export for advanced usage
|
|
191
|
+
export {
|
|
192
|
+
// Core functions
|
|
193
|
+
initialize,
|
|
194
|
+
shutdown,
|
|
195
|
+
getStatus,
|
|
196
|
+
flush,
|
|
197
|
+
|
|
198
|
+
// Config
|
|
199
|
+
getConfig,
|
|
200
|
+
isEnabled,
|
|
201
|
+
|
|
202
|
+
// Middleware for frameworks (optional, for better route extraction)
|
|
203
|
+
expressMiddleware,
|
|
204
|
+
expressErrorMiddleware,
|
|
205
|
+
fastifyPlugin,
|
|
206
|
+
fastifyErrorHandler,
|
|
207
|
+
|
|
208
|
+
// Utilities (for custom instrumentation)
|
|
209
|
+
normalizeRoute,
|
|
210
|
+
extractRoute,
|
|
211
|
+
RouteRegistry,
|
|
212
|
+
getAggregator,
|
|
213
|
+
|
|
214
|
+
// MongoDB instrumentation
|
|
215
|
+
instrumentMongoDB,
|
|
216
|
+
|
|
217
|
+
// OpenAI instrumentation
|
|
218
|
+
instrumentOpenAI,
|
|
219
|
+
|
|
220
|
+
// Circuit breaker
|
|
221
|
+
createBreaker,
|
|
222
|
+
wrapMongoOperation,
|
|
223
|
+
getBreakerStats,
|
|
224
|
+
|
|
225
|
+
// Tracing (W3C context + optional log correlation)
|
|
226
|
+
getActiveTraceId
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Default export
|
|
230
|
+
export default {
|
|
231
|
+
initialize,
|
|
232
|
+
shutdown,
|
|
233
|
+
getStatus,
|
|
234
|
+
flush,
|
|
235
|
+
expressMiddleware,
|
|
236
|
+
expressErrorMiddleware,
|
|
237
|
+
fastifyPlugin,
|
|
238
|
+
fastifyErrorHandler,
|
|
239
|
+
normalizeRoute,
|
|
240
|
+
extractRoute,
|
|
241
|
+
getAggregator,
|
|
242
|
+
// MongoDB
|
|
243
|
+
instrumentMongoDB,
|
|
244
|
+
// OpenAI
|
|
245
|
+
instrumentOpenAI,
|
|
246
|
+
// Circuit breaker
|
|
247
|
+
createBreaker,
|
|
248
|
+
wrapMongoOperation,
|
|
249
|
+
getBreakerStats,
|
|
250
|
+
getActiveTraceId
|
|
251
|
+
};
|
package/src/mongodb.js
CHANGED
|
@@ -106,6 +106,12 @@ function handleCommandStarted(event) {
|
|
|
106
106
|
|
|
107
107
|
const commandName = event.commandName;
|
|
108
108
|
|
|
109
|
+
// #region agent log
|
|
110
|
+
try {
|
|
111
|
+
console.log('[SG-APM-DBG]', JSON.stringify({sessionId:'ecc573',runId:'mongo-cmd',hypothesisId:'C',location:'mongodb.js:handleCommandStarted',message:'commandStarted event fired',data:{commandName,databaseName:event.databaseName,ignored:IGNORED_COMMANDS.has(commandName),requestId:String(event.requestId||'')},timestamp:Date.now()}));
|
|
112
|
+
} catch {}
|
|
113
|
+
// #endregion
|
|
114
|
+
|
|
109
115
|
// Skip ignored commands
|
|
110
116
|
if (IGNORED_COMMANDS.has(commandName)) {
|
|
111
117
|
return;
|
|
@@ -263,26 +269,42 @@ function collectPoolStats() {
|
|
|
263
269
|
* also populate require.cache via the CJS interop layer.
|
|
264
270
|
*/
|
|
265
271
|
function tryDetectMongoose() {
|
|
272
|
+
let cacheKeyCount = 0;
|
|
273
|
+
let mongooseKeyMatched = null;
|
|
274
|
+
let cacheError = null;
|
|
266
275
|
try {
|
|
267
276
|
// Strategy 1: Scan require.cache for mongoose
|
|
268
277
|
const cache = require.cache || {};
|
|
269
278
|
const cacheKeys = Object.keys(cache);
|
|
279
|
+
cacheKeyCount = cacheKeys.length;
|
|
270
280
|
const mongooseKey = cacheKeys.find(
|
|
271
281
|
k => /[\\/]mongoose[\\/](?:lib[\\/])?index\.js$/.test(k) &&
|
|
272
282
|
!k.includes('node_modules/mongoose/node_modules')
|
|
273
283
|
);
|
|
284
|
+
mongooseKeyMatched = mongooseKey || null;
|
|
274
285
|
if (mongooseKey && cache[mongooseKey]?.exports) {
|
|
275
286
|
const mod = cache[mongooseKey].exports;
|
|
276
287
|
// Verify it's actually mongoose (has connection property)
|
|
277
288
|
if (mod.connection) {
|
|
278
289
|
debug('Detected mongoose via require.cache');
|
|
290
|
+
// #region agent log
|
|
291
|
+
try {
|
|
292
|
+
console.log('[SG-APM-DBG]', JSON.stringify({sessionId:'ecc573',runId:'mongo-detect',hypothesisId:'D',location:'mongodb.js:tryDetectMongoose',message:'mongoose detected via require.cache',data:{isBun:typeof globalThis.Bun!=='undefined',cacheKeyCount,matchedKey:mongooseKey,readyState:mod.connection?.readyState??null},timestamp:Date.now()}));
|
|
293
|
+
} catch {}
|
|
294
|
+
// #endregion
|
|
279
295
|
return mod;
|
|
280
296
|
}
|
|
281
297
|
}
|
|
282
298
|
} catch (e) {
|
|
283
|
-
|
|
299
|
+
cacheError = e?.message || String(e);
|
|
284
300
|
}
|
|
285
301
|
|
|
302
|
+
// #region agent log
|
|
303
|
+
try {
|
|
304
|
+
console.log('[SG-APM-DBG]', JSON.stringify({sessionId:'ecc573',runId:'mongo-detect',hypothesisId:'D',location:'mongodb.js:tryDetectMongoose',message:'mongoose NOT detected (require.cache scan)',data:{isBun:typeof globalThis.Bun!=='undefined',hasRequire:typeof require!=='undefined',hasRequireCache:typeof require!=='undefined' && !!require.cache,cacheKeyCount,mongooseKeyMatched,cacheError,globalThisMongoose:!!globalThis.mongoose},timestamp:Date.now()}));
|
|
305
|
+
} catch {}
|
|
306
|
+
// #endregion
|
|
307
|
+
|
|
286
308
|
// Strategy 2: Fallback — check globalThis (in case user set it manually)
|
|
287
309
|
try {
|
|
288
310
|
if (globalThis.mongoose?.connection) {
|
|
@@ -306,6 +328,14 @@ function ensureMonitorCommands(client) {
|
|
|
306
328
|
client.options?.monitorCommands ||
|
|
307
329
|
client.s?.options?.monitorCommands;
|
|
308
330
|
|
|
331
|
+
// #region agent log
|
|
332
|
+
try {
|
|
333
|
+
const optsRoot = client.options ? Object.keys(client.options).slice(0, 30) : null;
|
|
334
|
+
const optsS = client.s?.options ? Object.keys(client.s.options).slice(0, 30) : null;
|
|
335
|
+
console.log('[SG-APM-DBG]', JSON.stringify({sessionId:'ecc573',runId:'mongo-init',hypothesisId:'C',location:'mongodb.js:ensureMonitorCommands',message:'monitorCommands inspection on live MongoClient',data:{alreadyEnabled:!!alreadyEnabled,hasClientOptions:!!client.options,hasClientSOptions:!!client.s?.options,clientOptionsFrozen:client.options ? Object.isFrozen(client.options) : null,clientSOptionsFrozen:client.s?.options ? Object.isFrozen(client.s.options) : null,optsRootKeys:optsRoot,optsSKeys:optsS},timestamp:Date.now()}));
|
|
336
|
+
} catch {}
|
|
337
|
+
// #endregion
|
|
338
|
+
|
|
309
339
|
if (alreadyEnabled) {
|
|
310
340
|
debug('monitorCommands already enabled');
|
|
311
341
|
return true;
|
|
@@ -347,6 +377,40 @@ function ensureMonitorCommands(client) {
|
|
|
347
377
|
}
|
|
348
378
|
}
|
|
349
379
|
|
|
380
|
+
/**
|
|
381
|
+
* Wrap mongoose.connect (and createConnection) to inject monitorCommands:true.
|
|
382
|
+
* The MongoDB driver reads monitorCommands at connect time; flipping it later is a no-op.
|
|
383
|
+
*/
|
|
384
|
+
function wrapMongooseConnectForMonitorCommands(mongoose) {
|
|
385
|
+
if (!mongoose || mongoose.__sentienguardConnectWrapped) return;
|
|
386
|
+
try {
|
|
387
|
+
if (typeof mongoose.connect === 'function') {
|
|
388
|
+
const origConnect = mongoose.connect.bind(mongoose);
|
|
389
|
+
mongoose.connect = function sentienguardConnect(uri, options, ...rest) {
|
|
390
|
+
const opts = { ...(options || {}), monitorCommands: true };
|
|
391
|
+
debug('Injecting monitorCommands:true into mongoose.connect()');
|
|
392
|
+
// #region agent log
|
|
393
|
+
try {
|
|
394
|
+
console.log('[SG-APM-DBG]', JSON.stringify({sessionId:'ecc573',runId:'post-fix',hypothesisId:'C',location:'mongodb.js:wrapMongooseConnect',message:'mongoose.connect called - injected monitorCommands:true',data:{hadOptions:!!options,userMonitorCommands:options?.monitorCommands??null},timestamp:Date.now()}));
|
|
395
|
+
} catch {}
|
|
396
|
+
// #endregion
|
|
397
|
+
return origConnect(uri, opts, ...rest);
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
if (typeof mongoose.createConnection === 'function') {
|
|
401
|
+
const origCreate = mongoose.createConnection.bind(mongoose);
|
|
402
|
+
mongoose.createConnection = function sentienguardCreateConnection(uri, options, ...rest) {
|
|
403
|
+
const opts = { ...(options || {}), monitorCommands: true };
|
|
404
|
+
return origCreate(uri, opts, ...rest);
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
mongoose.__sentienguardConnectWrapped = true;
|
|
408
|
+
debug('Wrapped mongoose.connect/createConnection with monitorCommands injector');
|
|
409
|
+
} catch (err) {
|
|
410
|
+
debug(`Failed to wrap mongoose.connect: ${err.message}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
350
414
|
/**
|
|
351
415
|
* Attach instrumentation to a detected mongoose instance.
|
|
352
416
|
* Handles waiting for the connection to be ready.
|
|
@@ -358,20 +422,19 @@ function attachToMongoose(mongoose) {
|
|
|
358
422
|
}
|
|
359
423
|
|
|
360
424
|
if (mongoose.connection.readyState === 1) {
|
|
361
|
-
// Already connected
|
|
425
|
+
// Already connected — too late to inject monitorCommands; instrument what we can.
|
|
362
426
|
instrumentMongoDB(mongoose);
|
|
363
|
-
} else if (mongoose.connection.readyState === 2) {
|
|
364
|
-
// Connecting — wait for it
|
|
365
|
-
mongoose.connection.once('connected', () => {
|
|
366
|
-
instrumentMongoDB(mongoose);
|
|
367
|
-
});
|
|
368
|
-
debug('Waiting for Mongoose connection to complete before instrumenting');
|
|
369
427
|
} else {
|
|
370
|
-
// Not connected
|
|
428
|
+
// Not yet connected (readyState 0 or 2): inject monitorCommands BEFORE connect runs.
|
|
429
|
+
wrapMongooseConnectForMonitorCommands(mongoose);
|
|
371
430
|
mongoose.connection.once('connected', () => {
|
|
372
431
|
instrumentMongoDB(mongoose);
|
|
373
432
|
});
|
|
374
|
-
debug(
|
|
433
|
+
debug(
|
|
434
|
+
mongoose.connection.readyState === 2
|
|
435
|
+
? 'Mongoose connecting — will instrument on connected event'
|
|
436
|
+
: 'Mongoose not yet connecting — will instrument on connected event'
|
|
437
|
+
);
|
|
375
438
|
}
|
|
376
439
|
}
|
|
377
440
|
|