@sentienguard/apm 1.0.8 → 1.0.10

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,225 +1,233 @@
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_FLUSH_INTERVAL=10 (optional, seconds)
18
- *
19
- * No config SDK disables itself silently.
20
- */
21
-
22
- import config, { isEnabled, debug, getConfig, loadConfig } from './config.js';
23
- import { instrumentHttp, expressMiddleware, fastifyPlugin } from './instrumentation.js';
24
- import { instrumentDependencies } from './dependencies.js';
25
- import { startErrorCapture, expressErrorMiddleware, fastifyErrorHandler } from './errors.js';
26
- import { startFlushing, stopFlushing, finalFlush, flush } from './transport.js';
27
- import { getAggregator } from './aggregator.js';
28
- import { normalizeRoute, extractRoute, RouteRegistry } from './normalizer.js';
29
- import { instrumentMongoDB, autoInstrumentMongoDB, stopMongoDBInstrumentation } from './mongodb.js';
30
- import { instrumentOpenAI, stopOpenAIInstrumentation } from './openai.js';
31
- import { createBreaker, wrapMongoOperation, getBreakerStats, shutdownBreakers } from './circuitBreaker.js';
32
-
33
- let isInitialized = false;
34
-
35
- /**
36
- * Initialize the SDK
37
- * Called automatically on import
38
- */
39
- function initialize() {
40
- if (isInitialized) {
41
- debug('SDK already initialized');
42
- return;
43
- }
44
-
45
- // Load config from env vars (lazy allows dotenv to load first)
46
- loadConfig();
47
-
48
- // Check if SDK should be enabled
49
- if (!isEnabled()) {
50
- // Silently disable - this is expected behavior
51
- debug('SDK disabled (missing API key or service name)');
52
- return;
53
- }
54
-
55
- // Warn if this import is not first (other modules may have created servers already)
56
- // We can't reliably detect this, so just log for debugging
57
- debug(`Initializing SDK for service: ${config.service}`);
58
- debug(`Environment: ${config.environment}`);
59
- debug(`Endpoint: ${config.endpoint}`);
60
- debug(`Flush interval: ${config.flushInterval}s`);
61
-
62
- // Instrument HTTP (incoming requests)
63
- instrumentHttp();
64
-
65
- // Instrument dependencies (outgoing requests)
66
- instrumentDependencies();
67
-
68
- // Auto-instrument MongoDB if available
69
- autoInstrumentMongoDB();
70
-
71
- // Start error capture
72
- startErrorCapture();
73
-
74
- // Start periodic flush
75
- startFlushing();
76
-
77
- // Handle graceful shutdown
78
- setupGracefulShutdown();
79
-
80
- isInitialized = true;
81
- debug('SDK initialized successfully');
82
- }
83
-
84
- /**
85
- * Setup graceful shutdown handlers
86
- */
87
- function setupGracefulShutdown() {
88
- const shutdown = async (signal) => {
89
- debug(`Received ${signal}, performing graceful shutdown`);
90
-
91
- // Stop accepting new data
92
- stopFlushing();
93
-
94
- // Final flush
95
- await finalFlush();
96
-
97
- debug('Shutdown complete');
98
- };
99
-
100
- // Handle common shutdown signals
101
- process.once('SIGTERM', () => shutdown('SIGTERM'));
102
- process.once('SIGINT', () => shutdown('SIGINT'));
103
-
104
- // Handle process exit
105
- process.once('beforeExit', () => shutdown('beforeExit'));
106
- }
107
-
108
- /**
109
- * Shutdown the SDK
110
- * Call this before process exit for clean shutdown
111
- */
112
- async function shutdown() {
113
- if (!isInitialized) return;
114
-
115
- debug('Shutting down SDK');
116
-
117
- // Stop MongoDB instrumentation
118
- stopMongoDBInstrumentation();
119
-
120
- // Stop OpenAI instrumentation
121
- stopOpenAIInstrumentation();
122
-
123
- // Shutdown circuit breakers
124
- shutdownBreakers();
125
-
126
- stopFlushing();
127
- await finalFlush();
128
-
129
- isInitialized = false;
130
- debug('SDK shutdown complete');
131
- }
132
-
133
- /**
134
- * Get current SDK status
135
- */
136
- function getStatus() {
137
- const aggregator = getAggregator();
138
- const stats = aggregator.getStats();
139
-
140
- return {
141
- enabled: isEnabled(),
142
- initialized: isInitialized,
143
- config: {
144
- service: config.service,
145
- environment: config.environment,
146
- flushInterval: config.flushInterval
147
- },
148
- stats
149
- };
150
- }
151
-
152
- // Phase 1: Immediately patch http/https (config-independent, must happen before servers are created)
153
- instrumentHttp();
154
- instrumentDependencies();
155
-
156
- // Phase 2: Try full initialization (works if env vars are already set at system/process level)
157
- initialize();
158
-
159
- // Phase 3: If init failed (env vars not loaded yet, e.g. dotenv.config() hasn't run),
160
- // retry after the importing module's synchronous code completes
161
- if (!isInitialized) {
162
- process.nextTick(() => {
163
- loadConfig({ force: true });
164
- initialize();
165
- });
166
- }
167
-
168
- // Export for advanced usage
169
- export {
170
- // Core functions
171
- initialize,
172
- shutdown,
173
- getStatus,
174
- flush,
175
-
176
- // Config
177
- getConfig,
178
- isEnabled,
179
-
180
- // Middleware for frameworks (optional, for better route extraction)
181
- expressMiddleware,
182
- expressErrorMiddleware,
183
- fastifyPlugin,
184
- fastifyErrorHandler,
185
-
186
- // Utilities (for custom instrumentation)
187
- normalizeRoute,
188
- extractRoute,
189
- RouteRegistry,
190
- getAggregator,
191
-
192
- // MongoDB instrumentation
193
- instrumentMongoDB,
194
-
195
- // OpenAI instrumentation
196
- instrumentOpenAI,
197
-
198
- // Circuit breaker
199
- createBreaker,
200
- wrapMongoOperation,
201
- getBreakerStats
202
- };
203
-
204
- // Default export
205
- export default {
206
- initialize,
207
- shutdown,
208
- getStatus,
209
- flush,
210
- expressMiddleware,
211
- expressErrorMiddleware,
212
- fastifyPlugin,
213
- fastifyErrorHandler,
214
- normalizeRoute,
215
- extractRoute,
216
- getAggregator,
217
- // MongoDB
218
- instrumentMongoDB,
219
- // OpenAI
220
- instrumentOpenAI,
221
- // Circuit breaker
222
- createBreaker,
223
- wrapMongoOperation,
224
- getBreakerStats
225
- };
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_FLUSH_INTERVAL=10 (optional, seconds)
18
+ * SENTIENGUARD_TRACING=false (optional, disable OpenTelemetry / W3C propagation; use legacy HTTP patches)
19
+ * SENTIENGUARD_TRACE_LOCAL_HTTP=true (optional, record outgoing HTTP to localhost as dependencies; use with SENTIENGUARD_PEER_SERVICE_MAP)
20
+ * SENTIENGUARD_PEER_SERVICE_MAP=3001:service-b,3002:other (optional, port -> callee name for local peers)
21
+ *
22
+ * No config SDK disables itself silently.
23
+ */
24
+
25
+ import config, { isEnabled, debug, getConfig, loadConfig } from './config.js';
26
+ import { instrumentHttp, expressMiddleware, fastifyPlugin } from './instrumentation.js';
27
+ import { instrumentDependencies, instrumentFetch } from './dependencies.js';
28
+ import { startErrorCapture, expressErrorMiddleware, fastifyErrorHandler } from './errors.js';
29
+ import { startFlushing, stopFlushing, finalFlush, flush } from './transport.js';
30
+ import { getAggregator } from './aggregator.js';
31
+ import { normalizeRoute, extractRoute, RouteRegistry } from './normalizer.js';
32
+ import { instrumentMongoDB, autoInstrumentMongoDB, stopMongoDBInstrumentation } from './mongodb.js';
33
+ import { instrumentOpenAI, stopOpenAIInstrumentation } from './openai.js';
34
+ import { createBreaker, wrapMongoOperation, getBreakerStats, shutdownBreakers } from './circuitBreaker.js';
35
+ import { startTracing, shutdownTracing, getActiveTraceId, isTracingActive } from './tracing.js';
36
+
37
+ let isInitialized = false;
38
+
39
+ /**
40
+ * Initialize the SDK
41
+ * Called automatically on import
42
+ */
43
+ function initialize() {
44
+ if (isInitialized) {
45
+ debug('SDK already initialized');
46
+ return;
47
+ }
48
+
49
+ // Load config from env vars (lazy — allows dotenv to load first)
50
+ loadConfig();
51
+
52
+ // Check if SDK should be enabled
53
+ if (!isEnabled()) {
54
+ // Silently disable - this is expected behavior
55
+ debug('SDK disabled (missing API key or service name)');
56
+ return;
57
+ }
58
+
59
+ // Warn if this import is not first (other modules may have created servers already)
60
+ // We can't reliably detect this, so just log for debugging
61
+ debug(`Initializing SDK for service: ${config.service}`);
62
+ debug(`Environment: ${config.environment}`);
63
+ debug(`Endpoint: ${config.endpoint}`);
64
+ debug(`Flush interval: ${config.flushInterval}s`);
65
+
66
+ // OpenTelemetry: W3C propagation + span → metrics (same ingest as before); legacy patches if this fails or is off
67
+ const tracingOn = startTracing();
68
+ // Ensure fetch() dependency edges are captured (works in both tracing and legacy modes)
69
+ instrumentFetch();
70
+ if (!tracingOn) {
71
+ instrumentHttp();
72
+ instrumentDependencies();
73
+ }
74
+
75
+ // Auto-instrument MongoDB if available
76
+ autoInstrumentMongoDB();
77
+
78
+ // Start error capture
79
+ startErrorCapture();
80
+
81
+ // Start periodic flush
82
+ startFlushing();
83
+
84
+ // Handle graceful shutdown
85
+ setupGracefulShutdown();
86
+
87
+ isInitialized = true;
88
+ debug('SDK initialized successfully');
89
+ }
90
+
91
+ /**
92
+ * Setup graceful shutdown handlers
93
+ */
94
+ function setupGracefulShutdown() {
95
+ const shutdown = async (signal) => {
96
+ debug(`Received ${signal}, performing graceful shutdown`);
97
+
98
+ // Stop accepting new data
99
+ stopFlushing();
100
+
101
+ // Final flush
102
+ await finalFlush();
103
+
104
+ debug('Shutdown complete');
105
+ };
106
+
107
+ // Handle common shutdown signals
108
+ process.once('SIGTERM', () => shutdown('SIGTERM'));
109
+ process.once('SIGINT', () => shutdown('SIGINT'));
110
+
111
+ // Handle process exit
112
+ process.once('beforeExit', () => shutdown('beforeExit'));
113
+ }
114
+
115
+ /**
116
+ * Shutdown the SDK
117
+ * Call this before process exit for clean shutdown
118
+ */
119
+ async function shutdown() {
120
+ if (!isInitialized) return;
121
+
122
+ debug('Shutting down SDK');
123
+
124
+ await shutdownTracing();
125
+
126
+ // Stop MongoDB instrumentation
127
+ stopMongoDBInstrumentation();
128
+
129
+ // Stop OpenAI instrumentation
130
+ stopOpenAIInstrumentation();
131
+
132
+ // Shutdown circuit breakers
133
+ shutdownBreakers();
134
+
135
+ stopFlushing();
136
+ await finalFlush();
137
+
138
+ isInitialized = false;
139
+ debug('SDK shutdown complete');
140
+ }
141
+
142
+ /**
143
+ * Get current SDK status
144
+ */
145
+ function getStatus() {
146
+ const aggregator = getAggregator();
147
+ const stats = aggregator.getStats();
148
+
149
+ return {
150
+ enabled: isEnabled(),
151
+ initialized: isInitialized,
152
+ tracing: isTracingActive(),
153
+ config: {
154
+ service: config.service,
155
+ environment: config.environment,
156
+ flushInterval: config.flushInterval
157
+ },
158
+ stats
159
+ };
160
+ }
161
+
162
+ // Initialize when import runs (retry on nextTick if dotenv loads env vars later)
163
+ initialize();
164
+
165
+ if (!isInitialized) {
166
+ process.nextTick(() => {
167
+ loadConfig({ force: true });
168
+ initialize();
169
+ });
170
+ }
171
+
172
+ // Export for advanced usage
173
+ export {
174
+ // Core functions
175
+ initialize,
176
+ shutdown,
177
+ getStatus,
178
+ flush,
179
+
180
+ // Config
181
+ getConfig,
182
+ isEnabled,
183
+
184
+ // Middleware for frameworks (optional, for better route extraction)
185
+ expressMiddleware,
186
+ expressErrorMiddleware,
187
+ fastifyPlugin,
188
+ fastifyErrorHandler,
189
+
190
+ // Utilities (for custom instrumentation)
191
+ normalizeRoute,
192
+ extractRoute,
193
+ RouteRegistry,
194
+ getAggregator,
195
+
196
+ // MongoDB instrumentation
197
+ instrumentMongoDB,
198
+
199
+ // OpenAI instrumentation
200
+ instrumentOpenAI,
201
+
202
+ // Circuit breaker
203
+ createBreaker,
204
+ wrapMongoOperation,
205
+ getBreakerStats,
206
+
207
+ // Tracing (W3C context + optional log correlation)
208
+ getActiveTraceId
209
+ };
210
+
211
+ // Default export
212
+ export default {
213
+ initialize,
214
+ shutdown,
215
+ getStatus,
216
+ flush,
217
+ expressMiddleware,
218
+ expressErrorMiddleware,
219
+ fastifyPlugin,
220
+ fastifyErrorHandler,
221
+ normalizeRoute,
222
+ extractRoute,
223
+ getAggregator,
224
+ // MongoDB
225
+ instrumentMongoDB,
226
+ // OpenAI
227
+ instrumentOpenAI,
228
+ // Circuit breaker
229
+ createBreaker,
230
+ wrapMongoOperation,
231
+ getBreakerStats,
232
+ getActiveTraceId
233
+ };
@@ -15,6 +15,7 @@ import https from 'https';
15
15
  import { extractRoute } from './normalizer.js';
16
16
  import { getAggregator } from './aggregator.js';
17
17
  import { debug } from './config.js';
18
+ import { isTracingActive } from './tracing.js';
18
19
 
19
20
  let isInstrumented = false;
20
21
  let originalHttpCreateServer = null;
@@ -130,6 +131,10 @@ export function instrumentHttp() {
130
131
  */
131
132
  export function expressMiddleware() {
132
133
  return function sentienguardMiddleware(req, res, next) {
134
+ if (isTracingActive()) {
135
+ return next();
136
+ }
137
+
133
138
  const startTime = process.hrtime.bigint();
134
139
 
135
140
  // Use res.on('finish') for Express
@@ -157,6 +162,11 @@ export function expressMiddleware() {
157
162
  * Create Fastify plugin for instrumentation
158
163
  */
159
164
  export function fastifyPlugin(fastify, options, done) {
165
+ if (isTracingActive()) {
166
+ done();
167
+ return;
168
+ }
169
+
160
170
  fastify.addHook('onRequest', async (request, reply) => {
161
171
  request.sentienguardStart = process.hrtime.bigint();
162
172
  });