@sentienguard/apm 1.0.21 → 1.0.23-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/src/index.js CHANGED
@@ -1,242 +1,245 @@
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
- if (!tracingOn) {
76
- instrumentHttp();
77
- instrumentDependencies();
78
- }
79
-
80
- // Auto-instrument MongoDB if available
81
- autoInstrumentMongoDB();
82
-
83
- // Start error capture
84
- startErrorCapture();
85
-
86
- // Start periodic flush
87
- startFlushing();
88
-
89
- // Handle graceful shutdown
90
- setupGracefulShutdown();
91
-
92
- isInitialized = true;
93
- debug('SDK initialized successfully');
94
- }
95
-
96
- /**
97
- * Setup graceful shutdown handlers
98
- */
99
- function setupGracefulShutdown() {
100
- const shutdown = async (signal) => {
101
- debug(`Received ${signal}, performing graceful shutdown`);
102
-
103
- // Stop accepting new data
104
- stopFlushing();
105
-
106
- // Final flush
107
- await finalFlush();
108
- // Best-effort flush of queued raw spans
109
- await flushTraceQueue();
110
-
111
- debug('Shutdown complete');
112
- };
113
-
114
- // Handle common shutdown signals
115
- process.once('SIGTERM', () => shutdown('SIGTERM'));
116
- process.once('SIGINT', () => shutdown('SIGINT'));
117
-
118
- // Handle process exit
119
- process.once('beforeExit', () => shutdown('beforeExit'));
120
- }
121
-
122
- /**
123
- * Shutdown the SDK
124
- * Call this before process exit for clean shutdown
125
- */
126
- async function shutdown() {
127
- if (!isInitialized) return;
128
-
129
- debug('Shutting down SDK');
130
-
131
- await shutdownTracing();
132
- // After OTel shutdown, best-effort drain any serialized spans still queued in transport.
133
- await flushTraceQueue();
134
-
135
- // Stop MongoDB instrumentation
136
- stopMongoDBInstrumentation();
137
-
138
- // Stop OpenAI instrumentation
139
- stopOpenAIInstrumentation();
140
-
141
- // Shutdown circuit breakers
142
- shutdownBreakers();
143
-
144
- stopFlushing();
145
- await finalFlush();
146
-
147
- isInitialized = false;
148
- debug('SDK shutdown complete');
149
- }
150
-
151
- /**
152
- * Get current SDK status
153
- */
154
- function getStatus() {
155
- const aggregator = getAggregator();
156
- const stats = aggregator.getStats();
157
-
158
- return {
159
- enabled: isEnabled(),
160
- initialized: isInitialized,
161
- tracing: isTracingActive(),
162
- config: {
163
- service: config.service,
164
- environment: config.environment,
165
- flushInterval: config.flushInterval
166
- },
167
- stats
168
- };
169
- }
170
-
171
- // Initialize when import runs (retry on nextTick if dotenv loads env vars later)
172
- initialize();
173
-
174
- if (!isInitialized) {
175
- process.nextTick(() => {
176
- loadConfig({ force: true });
177
- initialize();
178
- });
179
- }
180
-
181
- // Export for advanced usage
182
- export {
183
- // Core functions
184
- initialize,
185
- shutdown,
186
- getStatus,
187
- flush,
188
-
189
- // Config
190
- getConfig,
191
- isEnabled,
192
-
193
- // Middleware for frameworks (optional, for better route extraction)
194
- expressMiddleware,
195
- expressErrorMiddleware,
196
- fastifyPlugin,
197
- fastifyErrorHandler,
198
-
199
- // Utilities (for custom instrumentation)
200
- normalizeRoute,
201
- extractRoute,
202
- RouteRegistry,
203
- getAggregator,
204
-
205
- // MongoDB instrumentation
206
- instrumentMongoDB,
207
-
208
- // OpenAI instrumentation
209
- instrumentOpenAI,
210
-
211
- // Circuit breaker
212
- createBreaker,
213
- wrapMongoOperation,
214
- getBreakerStats,
215
-
216
- // Tracing (W3C context + optional log correlation)
217
- getActiveTraceId
218
- };
219
-
220
- // Default export
221
- export default {
222
- initialize,
223
- shutdown,
224
- getStatus,
225
- flush,
226
- expressMiddleware,
227
- expressErrorMiddleware,
228
- fastifyPlugin,
229
- fastifyErrorHandler,
230
- normalizeRoute,
231
- extractRoute,
232
- getAggregator,
233
- // MongoDB
234
- instrumentMongoDB,
235
- // OpenAI
236
- instrumentOpenAI,
237
- // Circuit breaker
238
- createBreaker,
239
- wrapMongoOperation,
240
- getBreakerStats,
241
- getActiveTraceId
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
+ // Auto-instrument MongoDB if available
84
+ autoInstrumentMongoDB();
85
+
86
+ // Start error capture
87
+ startErrorCapture();
88
+
89
+ // Start periodic flush
90
+ startFlushing();
91
+
92
+ // Handle graceful shutdown
93
+ setupGracefulShutdown();
94
+
95
+ isInitialized = true;
96
+ debug('SDK initialized successfully');
97
+ }
98
+
99
+ /**
100
+ * Setup graceful shutdown handlers
101
+ */
102
+ function setupGracefulShutdown() {
103
+ const shutdown = async (signal) => {
104
+ debug(`Received ${signal}, performing graceful shutdown`);
105
+
106
+ // Stop accepting new data
107
+ stopFlushing();
108
+
109
+ // Final flush
110
+ await finalFlush();
111
+ // Best-effort flush of queued raw spans
112
+ await flushTraceQueue();
113
+
114
+ debug('Shutdown complete');
115
+ };
116
+
117
+ // Handle common shutdown signals
118
+ process.once('SIGTERM', () => shutdown('SIGTERM'));
119
+ process.once('SIGINT', () => shutdown('SIGINT'));
120
+
121
+ // Handle process exit
122
+ process.once('beforeExit', () => shutdown('beforeExit'));
123
+ }
124
+
125
+ /**
126
+ * Shutdown the SDK
127
+ * Call this before process exit for clean shutdown
128
+ */
129
+ async function shutdown() {
130
+ if (!isInitialized) return;
131
+
132
+ debug('Shutting down SDK');
133
+
134
+ await shutdownTracing();
135
+ // After OTel shutdown, best-effort drain any serialized spans still queued in transport.
136
+ await flushTraceQueue();
137
+
138
+ // Stop MongoDB instrumentation
139
+ stopMongoDBInstrumentation();
140
+
141
+ // Stop OpenAI instrumentation
142
+ stopOpenAIInstrumentation();
143
+
144
+ // Shutdown circuit breakers
145
+ shutdownBreakers();
146
+
147
+ stopFlushing();
148
+ await finalFlush();
149
+
150
+ isInitialized = false;
151
+ debug('SDK shutdown complete');
152
+ }
153
+
154
+ /**
155
+ * Get current SDK status
156
+ */
157
+ function getStatus() {
158
+ const aggregator = getAggregator();
159
+ const stats = aggregator.getStats();
160
+
161
+ return {
162
+ enabled: isEnabled(),
163
+ initialized: isInitialized,
164
+ tracing: isTracingActive(),
165
+ config: {
166
+ service: config.service,
167
+ environment: config.environment,
168
+ flushInterval: config.flushInterval
169
+ },
170
+ stats
171
+ };
172
+ }
173
+
174
+ // Initialize when import runs (retry on nextTick if dotenv loads env vars later)
175
+ initialize();
176
+
177
+ if (!isInitialized) {
178
+ process.nextTick(() => {
179
+ loadConfig({ force: true });
180
+ initialize();
181
+ });
182
+ }
183
+
184
+ // Export for advanced usage
185
+ export {
186
+ // Core functions
187
+ initialize,
188
+ shutdown,
189
+ getStatus,
190
+ flush,
191
+
192
+ // Config
193
+ getConfig,
194
+ isEnabled,
195
+
196
+ // Middleware for frameworks (optional, for better route extraction)
197
+ expressMiddleware,
198
+ expressErrorMiddleware,
199
+ fastifyPlugin,
200
+ fastifyErrorHandler,
201
+
202
+ // Utilities (for custom instrumentation)
203
+ normalizeRoute,
204
+ extractRoute,
205
+ RouteRegistry,
206
+ getAggregator,
207
+
208
+ // MongoDB instrumentation
209
+ instrumentMongoDB,
210
+
211
+ // OpenAI instrumentation
212
+ instrumentOpenAI,
213
+
214
+ // Circuit breaker
215
+ createBreaker,
216
+ wrapMongoOperation,
217
+ getBreakerStats,
218
+
219
+ // Tracing (W3C context + optional log correlation)
220
+ getActiveTraceId
221
+ };
222
+
223
+ // Default export
224
+ export default {
225
+ initialize,
226
+ shutdown,
227
+ getStatus,
228
+ flush,
229
+ expressMiddleware,
230
+ expressErrorMiddleware,
231
+ fastifyPlugin,
232
+ fastifyErrorHandler,
233
+ normalizeRoute,
234
+ extractRoute,
235
+ getAggregator,
236
+ // MongoDB
237
+ instrumentMongoDB,
238
+ // OpenAI
239
+ instrumentOpenAI,
240
+ // Circuit breaker
241
+ createBreaker,
242
+ wrapMongoOperation,
243
+ getBreakerStats,
244
+ getActiveTraceId
245
+ };
package/src/mongodb.js CHANGED
@@ -347,6 +347,35 @@ function ensureMonitorCommands(client) {
347
347
  }
348
348
  }
349
349
 
350
+ /**
351
+ * Wrap mongoose.connect (and createConnection) to inject monitorCommands:true.
352
+ * The MongoDB driver reads monitorCommands at connect time; flipping it later is a no-op.
353
+ */
354
+ function wrapMongooseConnectForMonitorCommands(mongoose) {
355
+ if (!mongoose || mongoose.__sentienguardConnectWrapped) return;
356
+ try {
357
+ if (typeof mongoose.connect === 'function') {
358
+ const origConnect = mongoose.connect.bind(mongoose);
359
+ mongoose.connect = function sentienguardConnect(uri, options, ...rest) {
360
+ const opts = { ...(options || {}), monitorCommands: true };
361
+ debug('Injecting monitorCommands:true into mongoose.connect()');
362
+ return origConnect(uri, opts, ...rest);
363
+ };
364
+ }
365
+ if (typeof mongoose.createConnection === 'function') {
366
+ const origCreate = mongoose.createConnection.bind(mongoose);
367
+ mongoose.createConnection = function sentienguardCreateConnection(uri, options, ...rest) {
368
+ const opts = { ...(options || {}), monitorCommands: true };
369
+ return origCreate(uri, opts, ...rest);
370
+ };
371
+ }
372
+ mongoose.__sentienguardConnectWrapped = true;
373
+ debug('Wrapped mongoose.connect/createConnection with monitorCommands injector');
374
+ } catch (err) {
375
+ debug(`Failed to wrap mongoose.connect: ${err.message}`);
376
+ }
377
+ }
378
+
350
379
  /**
351
380
  * Attach instrumentation to a detected mongoose instance.
352
381
  * Handles waiting for the connection to be ready.
@@ -358,20 +387,19 @@ function attachToMongoose(mongoose) {
358
387
  }
359
388
 
360
389
  if (mongoose.connection.readyState === 1) {
361
- // Already connected
390
+ // Already connected — too late to inject monitorCommands; instrument what we can.
362
391
  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
392
  } else {
370
- // Not connected yet listen for future connection
393
+ // Not yet connected (readyState 0 or 2): inject monitorCommands BEFORE connect runs.
394
+ wrapMongooseConnectForMonitorCommands(mongoose);
371
395
  mongoose.connection.once('connected', () => {
372
396
  instrumentMongoDB(mongoose);
373
397
  });
374
- debug('Mongoose not yet connecting, will instrument when connected');
398
+ debug(
399
+ mongoose.connection.readyState === 2
400
+ ? 'Mongoose connecting — will instrument on connected event'
401
+ : 'Mongoose not yet connecting — will instrument on connected event'
402
+ );
375
403
  }
376
404
  }
377
405